Adding subclass inheritance outside a class when instantiating - python

I want to add the inheritance of one or more classes to another class depending on specific requirements; rather than creating multiple subclasses by hand, I want to be able to custom build them on the fly.
For example, the primary class, that would inherit from the others, is Sandwich:
class Sandwich(object):
def __init__(self):
super(Sandwich, self).__init__()
self.bread = "brown"
self.amount = 2
The base classes would be:
class Meat(object):
def __init__(self):
super(Meat, self).__init__()
self.ham = False
self.chicken = False
self.beef = False
class Vegetables(object):
def __init__(self):
super(Vegetables, self).__init__()
self.lettuce = False
self.onion = False
class Vegan(Vegetables):
def __init__(self, *args, **kwargs):
super(Vegetables, self).__init__(*args, **kwargs)
self.vegan_cheese = False
I want to create instances of these classes like this:
meat_sandwich = Sandwich(Meat)
veg_sandwich = Sandwich(Vegetables)
meat_and_veg_sandwich = Sandwich(Meat, Vegetables)
vegan_sandwich = Sandwich(Vegan)
I want to be able to access all variables and methods from these classes, and from the main class.
print(meat_sandwich.bread)
meat_sandwich.ham = True
I have found that you can assign a new class using __new__, however I have only succeeded in replacing the main class, and not setting up multiple inheritance / subclassing:
def __new__(cls, *args, **kwargs):
""" Set to assign any given subclass """
subcls = [i for i in args if isinstance(i, type)]
if subcls:
return super(Sandwich, cls).__new__(*subcls)
else:
return superSandwich, cls).__new__(cls)

Instead of using inheritance in my opinion it makes more sense to use composition here.
This will simplify the construction of the objects, and make it more flexible to change (such as adding new ingredients).
For example, each set of ingredient types could be a separate Enum:
from enum import Enum
class Bread(Enum):
WHITE = "white"
WHOLE_WHEAT = "whole wheat"
BROWN = "brown"
class Meat(Enum):
HAM = "ham"
CHICKEN = "chicken"
BEEF = "beef"
class Vegetable(Enum):
LETTUCE = "lettuce"
ONION = "onion"
class Vegan(Enum):
CHEESE = "vegan_cheese"
Now when defining the sandwich class you could do something like this:
class Sandwich:
def __init__(self, bread, ingredients=None):
self.bread = bread
self.ingredients = ingredients or []
To ask something like (as done in your example) if the sandwich contains ham, you could do:
>>> meat_sandwich = Sandwich(Bread.WHITE, [Meat.HAM])
>>> Meat.HAM in meat_sandwich.ingredients
True
To ask if the sandwich contains no meat, you could do:
>>> veg_sandwich = Sandwich(Bread.BROWN, [Vegetable.LETTUCE])
>>> all(not isinstance(i, Meat) for i in veg_sandwich.ingredients)
True

I've managed to get the results I was after, although I appreciate that I did not explain the problem very well, and suspect this might be something of a hack.
At any rate, I hope this helps illustrate what it was I was trying to achieve, and I would be interested in hearing alternate approaches to this solution.
meat_sandwich = type('meatSandwich_type', (Sandwich, Meat), dict())()
veg_sandwich = type('vegSandwich_type', (Sandwich, Vegetables), dict())()
meat_and_veg_sandwich = type('meatAndVegSandwich_type', (Sandwich, Meat, Vegetables), dict())()
vegan_sandwich = type('meatAndVegSandwich_type', (Sandwich, Vegan), dict())()

Related

Initialising nested classes in Python

Let's say I want to create a class 'House' that has some attributes of its own, but also has a (nested?) 'Resident' class which has some attributes and has a mandatory attribute 'surname'. A house instance may exist though without any residents. How can create this so that I can eventually do the following?
myhouse = House()
residentX = myhouse.resident('Smith')
Currently I set this up as a nested class but run into trouble when I try and initialise myhouse given that it is requiring a surname at this point for the nested Resident class (which I don't necessarily have at this point)
class House:
def __init__(self):
self.someattribute = <someattribute>
self.resident = self.Resident()
class Resident:
def __init__(self, surname):
self.surname = surname
I know I can restructure the code to not use nested classes and then explicitly tie any resident to a house in my code. However, I would like to use the dot notation here (myhouse.resident) to automatically tie a resident to a house.
Also, I understand that nested classes in python are somewhat frowned upon - I'm open to suggestions on how to do the above in a more pythonic manner.
I would break out the Resident class and use a property/setter for .resident
Like this:
class House:
def __init__(self):
self.someattribute = <someattribute>
self._resident = None
#property
def resident(self):
return self._resident
#resident.setter
def resident(self, surname):
r = Resident(surname)
self._resident = r
class Resident:
def __init__(self, surname):
self.surname = surname
However, if you want .resident to be callable but also want to track the house's residents, you can still break out the Resident class, and use:
class House:
def __init__(self):
self.someattribute = <someattribute>
self.residents = []
def resident(self, surname):
'''
Add a resident to the house
'''
r = Resident(surname)
self.residents.append(r)
return r
class Resident:
def __init__(self, surname):
self.surname = surname

Can I use a class insted of an instance of a class for better readability?

I have a situation where i can make an object as a class:
class BenjaminFranklin(Cat):
def __init__(self):
super().__init__()
self.ishungry = False
self.legs = 4
self.name_eng = 'Benjamin Franklin'
or an instance of a class:
benjamin_franklin = Cat()
benjamin_franklin.ishungry = False
benjamin_franklin.legs = 4
benjamin_franklin.name_eng = 'Benjamin Franklin'
The 'Correct' options seems to be using an instance of a class because there is no group of cats with the name "Benjamin Franklin"; there is only one and only cat. But it is much less readable, especially when using long names.
Tell me please:
"You can use class in this case for better readability, because..."
"The only correct option is to use an instance, because..."
Something else
Thanks
I assume, Benjamin Franklin is a single Cat. Therefore it should be an instance of the class cat.
One way you could do this would be:
class Cat:
def __init__(self, name_eng, is_hungry=False, legs=4):
self.ishungry = is_hungry
self.legs = legs
self.name_eng = name_eng
And then initialize your instance by:
benjamin_franklin = Cat("Benjamin Franklin") # is_hungry and legs do not to be passed, as they have the values you asked for defined as default
Rather than building benjamin_franklin the way you are, I would change your Cat constructor so it can take the arguments necessary to properly build out the object. Then, you could do something like benjamin_franklin = Cat('Benjamin Franklin') which would be much more readable. Such a constructor would look like:
def __init__(self, name_eng, is_hungry = False, legs = 4):
self.ishungry = is_hungry
self.legs = legs
self.name_eng = name_eng
However, if that is not an option, you could embed your code into a factory method:
def BenjaminFranklin():
benjamin_franklin = Cat()
benjamin_franklin.ishungry = False
benjamin_franklin.legs = 4
benjamin_franklin.name_eng = 'Benjamin Franklin'
return benjamin_franklin
If you have an object with many attributes on it, you could also try encapsulating the object into sub-objects and passing those in on your constructor. For instance, suppose your Cat had an identity with a name_eng and name_cha:
class Identity:
def __init__(self, name_eng, name_cha):
self.name_eng = name_eng
self.name_cha = name_cha
class Cat:
def __init__(self, id, ishungry = False, legs = 4):
self.identity = id
self.is_hungry = ishungry
self.legs = legs
which you could initialize like this:
benjamin_franklin = Cat(Identity('Benjamin Franklin', '猫'))
this is somewhat more verbose but it means that you can spread out your construction over several objects (and maybe create some constant objects that are the same over your most instantiations of your class).
Neither option feels right. The name BenjaminFranklin suggests that this is an instance of something. However, the question is why no_legs, is_hungry etc are specific to Benjamin Franklin, and not to all cats. It seems that those attributes should me moved up to the parent Cat class, and Benjamin Franklin should be an instance of it.
Moreover, if only benjamin franklin has a num_legs variable, what does he do with it? There is no added function that uses the new fields. In that sense, this seems more like a dict, or a namedtuple than an instance of a class.
However, it's not possible to tell what the best way is without seeing the functionality of the Cat class, and how you use the extra attributes of benjamin franklin.

Python inheritance - can you have a default value in a parent?

If I had a parent class attribute that all of the child classes are going to inherit, can I set a default so that when the object is created, it automatically take the default from the parent class and no argument has to be given when creating it?
class F1(object):
def __init__(self, sick="flu"):
self.sick = sick
class F2(F1):
def __init__(self, sick, cure):
super(F2, self).__init__(sick)
self.cure = cure
a = F2("bed rest")
print(a.sick)
print(a.cure)
this is just a sample bit of code to show what I mean. I want every child to inherit the "sick" from the parent so I do not have to send that argument in when creating the object. Is this possible? Is there a different way of doing this same thing? Would it be better to make "sick" a class attribute?
the problem with your code is, that you are declaring F2.__init__ to have two explicit arguments, even though you only want to pass one.
If you want to be able to optionally override the creation argument of F1 you need to handle that yourself (see F3)
class F1(object):
def __init__(self, sick="flu"):
self.sick = sick
class F2(F1):
def __init__(self, cure):
super(F2, self).__init__()
self.cure = cure
class F3(F1):
def __init__(self, cure, sick=None):
if sick is None:
super(F3, self).__init__()
else:
super(F3, self).__init__(sick)
self.cure = cure
a = F2("bed rest")
print("%s -> %s" % (a.sick, a.cure))
b = F3("inhale")
print("%s -> %s" % (b.sick, b.cure))
c = F3(sick="curiosity", cure="none")
print("%s -> %s" % (c.sick, c.cure))
Using super is the standard way of doing this in Python. If you want to override, just override...
class F2(F1):
def __init__(self, sick, cure):
super(F2, self).__init__(sick)
self.cure = cure
self.sick = sick + 1
Adding class attribute could be an option, depending on your need. From your description, I'd say it sounds better, because the default value of sick never change, and that's probably what you need.
Using class attribute does not affect overriding, because when assigning attribute on an instance, class attribute is not touched. An example:
>>> class F:
... a = 1
...
>>> f1, f2 = F(), F()
>>> f2.a = 2
>>> f1.a
1

Factory method for objects with many parameters

Let me introduce my problem. I am creating a set of objects which are generally Food, but each of them might have completely different set of attributes to set.
I thought to use Factory design pattern, then i faced a problem where and how to set objects attributes and then i found some Builder pattern. However i am not sure if i am at the right path.
Example:
class Food(object):
def __init__(self, taste = None):
self._taste = taste
class Bread(Food):
def __init__(self, flour_type = None):
Food.__init__(self, taste = 'good')
self._flour = flour_type
class Meat(Food):
def __init__(self, type = None, energy_value = None, taste = None):
Food.__init__(self, taste = taste)
self._type = type
self._energy = energy_value
class Soup(Food):
def __init__(self, name = None, recipe = None):
Food.__init__(self, taste = 'fine')
self._name = name
self._recipe = recipe
and then i have a factory like this:
FOOD_TYPES = {'food':Food, 'bread':Bread, 'meat':Meat, 'soup':Soup}
class FoodFactory(object):
#staticmethod
def create_food(food_type):
try:
return FOOD_TYPES[food_type.lower()]()
except Exception:
return None
My question is: I want to pass parameters for constructors but dont know how. Is Builder pattern good idea here or not? The list of attributes might be longer. I was also wondering if passing a context dictionary with attribute name as a key and value as value.
Any ideas how to solve this? Really appreciate any hints and tips.
Regards
Just edit your FoodFactory like this:
class FoodFactory(object):
#staticmethod
def create_food(food_type, **kwargs):
try:
return FOOD_TYPES[food_type.lower()](**kwargs)
except Exception:
return None
Now you can use keyworded arguments for each Food class:
>>> meat = FoodFactory.create_food("meat", energy_value=25)
>>> print meat
>>> print meat._energy
Will print out something like:
>>> <__main__.Meat object at 0x03122AB0>
>>> 25
Hope this helps!

Replacing member objects with subclasses in Python

I have the following problem that I will attempt to illustrate with the following example.
class Brick():
def __init__(self):
self.weight = 1
class House():
def __init__(self, number_bricks):
self.bricks = [Brick() for i in range(number_bricks)]
def get_weight(self):
return reduce(lambda x,y: x+y, [brick.weight for brick in self.bricks])
But now suppose I create a new kind of Brick, StrongBrick, so that I make a house, a subclass StrongHouse, where StrongBrick plays exactly the same role in StrongHouse as Brick plays in House. How can I do this in a nice way (not just retyping all the class definitions)?
So the basic idea is, how can I change a class which is composed of some objects to the same class but composed of say a subclass of the original member objects?
Thanks very much for any help you can give me.
You could have a factory (a brickyard?) and pass that to House.__init__().
class Brick(object): pass
class StrongBrick(Brick): pass
class House(object):
def __init__(self, brick_factory, num_bricks):
self.bricks = [brick_factory() for i in range(num_bricks)]
house = House(Brick, 10000)
strong_house = House(StrongBrick, 10000)
As you can see, subclassing House isn't even necessary to be able to construct houses from different types of bricks.
There are various ways to do this. You could make the relevant Brick class an attribute of the House class:
class House(object):
brick_class = Brick
def __init__(self, number_bricks):
self.bricks = [self.brick_class() for i in range(number_bricks)]
class StrongHouse(House):
brick_class = StrongBrick
Or, you could pass in the Brick class you want to use when constructing the House:
class House(object):
def __init__(self, brick_class, number_bricks):
self.bricks = [brick_class() for i in range(number_bricks)]
One nice pattern could be this:
class Brick(object):
weight = 1
class StrongBrick(Brick):
weight = 42
class House(object):
brick_type = Brick
def __init__(self, number_bricks):
self.bricks = [self.brick_type() for i in range(number_bricks)]
def get_weight(self):
return reduce(lambda x, y: x + y, [brick.weight for brick in self.bricks])
class StrongHouse(House):
brick_type = StrongBrick
Another is to make a function making a factory, and using an argument for the brick_type with default value:
class House(object):
def __init__(self, number_bricks, brick_type=Brick):
self.bricks = [brick_type() for i in range(number_bricks)]
def get_weight(self):
return reduce(lambda x, y: x + y, [brick.weight for brick in self.bricks])
def make_house_factory(brick_type):
def factory(number_bricks):
return House(number_bricks, brick_type)
return factory
StrongHouse = make_house_factory(StrongBrick)
Of course all such objects would be instances of the House only, even though I named StrongHouse here so that it resembles a class name.
But now suppose I create a new kind of Brick, StrongBrick, so that I make a house, a subclass StrongHouse, where StrongBrick plays exactly the same role in StrongHouse as Brick plays in House. How can I do this in a nice way (not just retyping all the class definitions)?
As all of the other answers have explained, you really don't want to create this parallel hierarchy at all. But to answer your direct question: You can create classes dynamically, so you can create a parallel hierarchy without copying and pasting all the class definitions. Classes are, after all, first-class objects.
Again, let me stress that you almost certainly don't want to do this, and I'm just showing that it is possible.
def make_house_class(brick_type):
class NewHouse(House):
def __init__(self, number_bricks):
self.bricks = [brick_type() for i in range(number_bricks)]
return NewHouse
Now, you could statically create all the house types:
StrongHouse = make_house_class(StrongBrick)
CheapHouse = make_house_class(CheapHouse)
# ...
… or maybe build them dynamically from a collection of all of your brick type:
brick_types = (StrongBrick, CheapBrick)
house_types = {brick_type: make_house_class(brick_type) for brick_type in brick_types}
… or even add some hacky introspection to just create a new FooHouse type for every FooBrick type in the current module:
for name, value in globals().items():
if name.endswith('Brick') and name != 'Brick' and isinstance(value, type):
globals()[name.replace('Brick', 'House')] = make_house_class(value)
… or even create them on the fly as needed in the factory-maker:
def make_house_factory(brick_type):
house_type = make_house_class(brick_type)
def factory(number_bricks):
return house_type(number_bricks, brick_type)
return factory
… or even the generated factory:
def make_house_factory(brick_type):
def factory(number_bricks):
return make_house_class(brick_type)(number_bricks, brick_type)
return factory
Add a parameter to the House.__init__ so that you can specify the Brick type:
import functools
class Brick():
def __init__(self):
self.weight = 1
class StrongBrick():
def __init__(self):
self.weight = 10
class House():
def __init__(self, number_bricks,brick_type=Brick):
self.bricks = [brick_type() for i in range(number_bricks)]
def get_weight(self):
return reduce(lambda x,y: x+y, [brick.weight for brick in self.bricks])
#not a new class, but an alias with a different default brick_type
StrongHouse = functools.partial(House,brick_type=StrongBrick)

Categories

Resources