How to avoid parallel class hierarchy in python - python

I've been running into a weird little smell in my python code lately and I think it has something to do with parallel inheritance. Here is a small example I concocted:
class DogHabits:
def __init__(self):
self.habits = ['lick butt']
class GermanShepherdHabits(DogHabits):
def __init__(self):
super().__init__()
self.habits.extend(['herd sheep'])
class LabradorHabits(DogHabits):
def __init__(self):
super().__init__()
self.habits.extend(['hunt', 'pee on owner'])
class Dog:
def __init__(self):
self.type = 'generic_dog'
self.my_habits = DogHabits()
def do_stuff(self):
for habit in self.my_habits.habits:
print(habit)
class GermanShepherd(Dog):
def __init__(self):
self.type = 'german shepherd'
self.my_habits = GermanShepherdHabits()
class Labrador(Dog):
def __init__(self):
self.type = 'labrador'
self.my_habits = LabradorHabits()
if __name__ == "__main__":
german_shepherd = GermanShepherd()
print('\n{}'.format(german_shepherd.type))
german_shepherd.do_stuff()
labrador = Labrador()
print('\n{}'.format(labrador.type))
labrador.do_stuff()
I have a generic dog class from which concrete dog implementations inherit. Every dog class (including the generic/abstract one) has a set of habits, itself represented by another class hierarchy for the habits.
I am annoyed by the fact that I have to have both hierarchies exactly the same at all times. Furthermore the inheritance between the DogHabits is useful within the habits hierarchy, but it is not useful within the dogs hierarchy, as I need to instantiate a separate habits object for each class in the dog hierarchy.
What is the antidote to this? I may want to add many implementations of the dog class, and updating the corresponding habits hierarchy sounds tedious and smells bad...

This might be going too far afield, but I don't see the need for a separate DogHabits class. habits should be a class attribute, not an instance attribute, and could be set by __init_subclass__.
class Dog:
habits = ['lick butts']
def __init_subclass__(cls, habits=None, **kwargs):
super().__init_subclass__(**kwargs)
if habits is not None:
cls.habits = cls.habits + habits
class GermanShepherd(Dog, habits=['herd sheep']):
def __init__(self):
self.type = 'german shepherd'
class Labrador(Dog, habits=['pee on owner']):
def __init__(self):
self.type = 'labrador'
type itself is also more of a class attribute than an instance attribute, as it's simply an (alternate) string representation of information already encoded by the class itself. Since you wouldn't append to an existing value, it's easier to just set the class attribute where necessary rather than going through __init_subclass:
class Dog:
habits = ['lick butts']
type = 'generic_dog'
def __init_subclass__(cls, habits=None, **kwargs):
super().__init_subclass__(**kwargs)
if habits is not None:
cls.habits = cls.habits + habits
class GermanShepherd(Dog, habits=['herd sheep']):
type = 'german shepard'
class Labrador(Dog, habits=['pee on owner']):
type = 'labrador'
class BlackLabrador(Labrador):
pass # E.g. if you are happy with inheriting Labrador.type

IF habits need to a class attribute, rather than instance attributes, this may actually be a good use for metaclasses.
Habits need not be a simple list, it could be something else, as long as there is the notion of addition to previous and return new. (__add__ or __radd__ on a Habits class would do the trick I think)
class DogType(type):
def __init__(cls, name, bases, attrs):
""" this is called at the Dog-class creation time. """
if not bases:
return
#pick the habits of direct ancestor and extend it with
#this class then assign to cls.
if "habits" in attrs:
base_habits = getattr(bases[0], "habits", [])
cls.habits = base_habits + cls.habits
class Dog(metaclass=DogType):
habits = ["licks butt"]
def __repr__(self):
return f"My name is {self.name}. I am a {self.__class__.__name__} %s and I like to {self.habits}"
def __init__(self, name):
""" dog instance can have all sorts of instance variables"""
self.name = name
class Sheperd(Dog):
habits = ["herds sheep"]
class GermanSheperd(Sheperd):
habits = ["bites people"]
class Poodle(Dog):
habits = ["barks stupidly"]
class StBernard(Dog):
pass
for ix, cls in enumerate([GermanSheperd, Poodle, StBernard]):
name = f"dog{ix}"
dog = cls(name)
print(dog)
output:
My name is dog0. I am a GermanSheperd %s and I like to ['licks butt', 'herds sheep', 'bites people']
My name is dog1. I am a Poodle %s and I like to ['licks butt', 'barks stupidly']
My name is dog2. I am a StBernard %s and I like to ['licks butt']

This answer assumes that the DogHabits is much more complex than a mere list and is really worth a dedicated class with its own inheritance.
On a design point of view, I can see a first question on whether habits and type should be class or instance members. Here again, this answer assumes that there are reasons to make them instance members.
I would make Habits an inner class of Dogs and state in the class documentation that is can be customized by building a subclass of it in a subclass of Dogs:
class Dog:
class Habits:
"""Represents the habits of a Dog.
It can be customized in a child class by creating in the subclass an
inner class named Habits that would be a subclass of Dog.Habits
"""
def __init__(self):
self.habits = ['lick butt']
def __init__(self, typ='generic_dog'):
self.type = typ
self.my_habits = self.__class__.Habits()
def do_stuff(self):
for habit in self.my_habits.habits:
print(habit)
class GermanShepherd(Dog):
class Habits(Dog.Habits):
def __init__(self):
super().__init__()
self.habits.extend(['herd sheep'])
def __init__(self):
super().__init__('german shepherd')
class Labrador(Dog):
class Habits(Dog.Habits):
def __init__(self):
super().__init__()
self.habits.extend(['hunt', 'pee on owner'])
def __init__(self):
super().__init__('labrador')

Related

Want a class that will behave like an ABC but also a metaclass

One of the answers to a previous question I asked suggests the use of a metaclass.
class DogType(type):
def __init__(cls, name, bases, attrs):
""" this is called at the Dog-class creation time. """
if not bases:
return
#pick the habits of direct ancestor and extend it with
#this class then assign to cls.
if "habits" in attrs:
base_habits = getattr(bases[0], "habits", [])
cls.habits = base_habits + cls.habits
class Dog(metaclass=DogType):
habits = ["licks butt"]
def __repr__(self):
return f"My name is {self.name}. I am a {self.__class__.__name__} %s and I like to {self.habits}"
def __init__(self, name):
""" dog instance can have all sorts of instance variables"""
self.name = name
class Sheperd(Dog):
habits = ["herds sheep"]
class GermanSheperd(Sheperd):
habits = ["bites people"]
class Poodle(Dog):
habits = ["barks stupidly"]
class StBernard(Dog):
pass
for ix, cls in enumerate([GermanSheperd, Poodle, StBernard]):
name = f"dog{ix}"
dog = cls(name)
print(dog)
However this throws an error:
TypeError: metaclass conflict: the metaclass of a derived class must
be a (non-strict) subclass of the metaclasses of all its bases
I like this solution, however I also really need class Dog to behave like a metaclass such that I can define abstract methods in Dog that will need to propagate in all subclasses. This could be a method like def bark() which all sub-dogs would need to implement...
How do I get Dog to be both a metaclass implementing the functionality in DogType, but also an Abstract class of its own accord which restricts how subclasses are instantiated and run?
If you look at the source code for the ABC class, you'll find that it's a simple instance of the ABCMeta class, which is why your example gave a metaclass conflict Thus, from your example, you can achieve it by
from abc import ABCMeta, abstractmethod
class Meta(ABCMeta):
pass
class BaseClass(metaclass=Meta):
#abstractmethod
def something(self):
pass
class DerivedClass(BaseClass):
def something(self):
return 1
try:
BaseClass()
except TypeError:
pass
else:
raise Exception('Meta class failed')
DerivedClass()
And you can see that this program runs just fine.
#MisterMiyagi's comments have provided some food for thought and also allowed me to better understand how ABC's themselves are implemented. It seems the "Abstract to Concrete" order is something like this:
type --> ABCMeta --> ABC --> regular class --> regular subclass --> object
This being said, if DogType inherits from ABCMeta as opposed to from type, it can still act as a metaclass, all the while allowing its subclasses to act as abstract base classes, since ABCMeta is the metaclass for any ABC. This allows us to do the following:
class DogType(ABCMeta):
def __init__(cls, name, bases, attrs):
""" this is called at the Dog-class creation time. """
if not bases:
return
#pick the habits of direct ancestor and extend it with
#this class then assign to cls.
if "habits" in attrs:
base_habits = getattr(bases[0], "habits", [])
cls.habits = base_habits + cls.habits
class Dog(metaclass=DogType):
habits = ["licks butt"]
def __repr__(self):
return f"My name is {self.name}. I am a {self.__class__.__name__} %s and I like to {self.habits}"
def __init__(self, name):
""" dog instance can have all sorts of instance variables"""
self.name = name
#abstractmethod
def print_habits(self):
for habit in self.habits:
print(habit)
class Sheperd(Dog):
habits = ["herds sheep"]
def print_habits(self):
for habit in self.habits:
print(habit)
class GermanSheperd(Sheperd):
habits = ["bites people"]
def print_habits(self):
for habit in self.habits:
print(habit)
class Poodle(Dog):
habits = ["barks stupidly"]
def print_habits(self):
for habit in self.habits:
print(habit)
class StBernard(Dog):
def print_habits(self):
for habit in self.habits:
print(habit)
for ix, cls in enumerate([GermanSheperd, Poodle, StBernard]):
name = f"dog{ix}"
print('\n', name)
print(cls)
dog = cls(name)
dog.print_habits()
I am aware that it is unclear in the above code why I am defining print_habits as an abstractmethod and reimplementing it in subclasses. I could simply define it once in Dog and it would be just fine, but in my use case there are methods that need to be enforced in all Dog subclasses, and some that don't.

How to instantiate a class given its parent class in Python?

I have 3 subclasses and 1 parent class that make the children share a common method.
Example:
class Animal:
def communicate():
pass
class Dog(Animal):
def communicate():
bark()
class Cat(Animal):
def communicate():
meow()
I would like to provide an API that instantiates a cat or a dog based on the received string that will be either "cat" or "dog" and calls .communicate(), but I don't want to write if and elses to check whether I should run Dog() or Cat(). I wonder if it is possible to bark or meow by doing something like:
Animal("dog").communicate()
Where "dog" can be a variable.
Or if possible give the child classes some labelling and be able to instantiate them via this label, or even via the own class name.
The ideia is to not have to write conditions Everytime I define new child child classes.
Thanks in advance!
Factory pattern is your solution.
Aproach to automate conditions for creating classes described here
I can show how metaclasses can be applied:
class MetaAnimal(type):
classes = {}
def __new__(cls, name, bases, dct):
result = super().__new__(cls, name, bases, dct)
cls.classes[name.lower()] = result
return result
#classmethod
def get_animal(cls, name):
return cls.classes.get(name)
class Animal(metaclass=MetaAnimal):
def communicate(self):
pass
class Dog(Animal):
def communicate(self):
self.bark()
def bark(self):
print('Woof')
class Cat(Animal):
def communicate(self):
self.meow()
def meow(self):
print('Meow')
MetaAnimal.get_animal('cat')().communicate()
MetaAnimal.get_animal('dog')().communicate()

Hide/Encapsulate all attributes from an inherited class while keeping functionality

Is there a way to hide or nest the Cat attributes within Animal while still being able to run the jump function? Specifically I would like to the output of vars(test) to only be the age. I'm sure I could hard code which specific attributes to not output by defining a custom __str__ but I will have a lot of attributes for Cat/Animal and I don't want to manually add an exception for each individual attribute. I also won't have access to the Cat class.
def Jump():
print('Jumped!')
class Cat:
def __init__(self):
self.feet = 4
self.jump = Jump
class Animal(Cat):
def __init__(self):
Cat.__init__(self)
self.age = 3
test = Animal()
test.jump()
print(vars(test))
Output:
Jumped {'feet': 4, 'jump': , 'age': 3}
This is just code to illustrate what I'm trying to do. In reality Cat represents a published python module and Animal represents my custom one.
If you change your relationship so that Animal no longer is-a Cat, but instead has-a Cat, then you're right, animal.jump() will no longer work.
There are many ways around this. Given how odd this design already is, in so many different ways, I have no idea which is most appropriate, so I'll just list a whole bunch of them.
The first group are ways to explicitly delegate just jump.
The "normal" way:
class Animal:
def __init__(self):
self.cat = Cat()
def jump(self):
return self.cat.jump()
Copy the per-instance function:
class Animal:
def __init__(self):
self.cat = Cat()
self.jump = self.cat.jump
Delegate to the per-instance function:
class Animal:
def __init__(self):
self.cat = Cat()
self.jump = lambda: self.cat.jump()
Per-instance bound method:
class Animal:
def __init__(self):
self.cat = Cat()
self.jump = (lambda self: self.cat.jump()).__get__(self)
Dynamic lookup:
class Animal:
def __init__(self):
self.cat = Cat()
def __getattr__(self, name):
if name == 'jump':
return getattr(self.cat, name)
raise AttributeError
Dynamic bound method generation:
class Animal:
def __init__(self):
self.cat = Cat()
def __getattr__(self, name):
if name == 'jump':
return (lambda self: getattr(self.cat, name)()).__get__(self)
raise AttributeError
Of course all of these only delegate jump specifically. What if you wanted to delegate to all Cat functions, methods, and maybe other attributes, without necessarily knowing what they are in advance? Well, it should be obvious how to adapt most of these, so I'll just show two.
Dynamic lookup:
class Animal:
def __init__(self):
self.cat = Cat()
def __getattr__(self, name):
return getattr(self.cat, name)
Semi-static inspection that does complicated reflection on the different possible kinds of things we might want to delegate:
class Animal:
def __init__(self):
self.cat = cat
for name, value in inspect.getmembers(self.cat):
if name.startswith('_'): continue
if inspect.ismethod(value):
value = (lambda self: value()).__get__(self)
elif callable(value):
value = lambda: value()
else:
value = copy.copy(value)
setattr(self, name, value)

Base class accessing super class variables

I'm using python 3.6.
My goal is to make a base class that would be able to somehow access through polymorphism - one of the child class variables.
I know it sounds somewhat 'not oop', so if what im describing can't be done with python - I would like to know what is the best practice for this case.
Following wikipedia's example:
class Animal:
def __init__(self, name): # Constructor of the class
self.name = name
def talk(self): # Abstract method, defined by convention only
raise NotImplementedError("Subclass must implement abstract method")
class Cat(Animal):
def talk(self):
return 'Meow!'
class Dog(Animal):
def talk(self):
return 'Woof! Woof!'
animals = [Cat('Missy'),
Cat('Mr. Mistoffelees'),
Dog('Lassie')]
for animal in animals:
print animal.name + ': ' + animal.talk()
Prints the following:
Missy: Meow!
Mr. Mistoffelees: Meow!
Lassie: Woof! Woof!
I would like to achieve the exactly same output - using
variable overloading (is that a thing?) instead of method overloading.
The reason is that in the programm im working on - dog, cat, and every other kind of animal will talk exactly the same way - influenced only by the data member, such as:
class Animal:
def __init__(self, name): # Constructor of the class
self.name = name
self.vocabulary = [] # so called abstract data member
def talk(self): # Non Abstract method, all animals would talk
for word in self.vocabulary: print (word)
class Cat(Animal):
vocabulary = ["Meow", "Muuuew", "Maow"]
class Dog(Animal):
vocabulary = ["Woof", "Waf", "Haw"]
animals = [Cat('Missy'),
Cat('Mr. Mistoffelees'),
Dog('Lassie')]
for animal in animals:
print animal.name + ': ' + animal.talk()
Prints the following:
Missy: Meow Muuuew Maow
Mr. Mistoffelees: Meow Muuuew Maow
Lassie: Woof Waf Haw
Clearly, this won't work since vocabulary will be empty, as it is in the base class.
I tried to find a solution using super, e.g:
class Cat(Animal):
vocabulary = ["Meow", "Muuuew", "Maow"]
def talk(self):
super(Animal,Cat).talk()
But the result would be AttributeError: 'super' object has no attribute 'talk'
Am I using super wrong?
There are a few unresolved issues in your code, but since python is so dynamic, it will find the subclass instance attribute through normal lookup:
class Animal:
def __init__(self, name):
self.name = name
def talk(self):
for word in self.vocabulary: print (word)
class Cat(Animal):
def __init__(self, name):
super().__init__(name)
self.vocabulary = ["Meow", "Muuuew", "Maow"]
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
self.vocabulary = ["Woof", "Waf", "Haw"]
animals = [Cat('Missy'),
Cat('Mr. Mistoffelees'),
Dog('Lassie')]
for animal in animals:
print(animal.name, end=': ')
animal.talk()
If you want something to enforce this requirement more explicitly in the code, you can make Animal an abstract base class and make an abstruct property named vocabulary:
import abc
class Animal(abc.ABC):
def __init__(self, name):
self.name = name
#property
#abc.abstractmethod
def vocabulary(self):
...
def talk(self):
for word in self.vocabulary: print (word)
class Cat(Animal):
#property
def vocabulary(self):
return ["Meow", "Muuuew", "Maow"]
here is a live link
Python is dynamically typed. There is no need to somehow declare an "abstract data member" in Animal for Animal methods to refer to self.vocabulary; in fact, your attempts to declare an "abstract data member" are causing your problems.
Just remove self.vocabulary = [], and talk will automatically find the subclass vocabulary when it tries to access self.vocabulary.

Difference between assigning values to attributes in child class and parent class

Choice A:
class Mammal(object):
def __init__(self, name):
self.name = name
def __str__(self):
return str(self.name)
class Human(Mammal):
def __init__(self, name):
self.name = name
me = Human("John")
print(me)
Choice B:
class Mammal(object):
def __init__(self, name):
self.name = name
def __str__(self):
return str(self.name)
class Human(Mammal):
def __init__(self, name):
super(Human, self).__init__(name)
me = Human("John")
print(me)
Both choices return the same result, but can someone please explain what's the difference between assigning the name to child class (Human) and the parent class (Mammal)? Is there a better one between these two choices?
Thank you very much!
This is really a question of class design and maintenance. Obviously in this case there are no characteristics that Humans have that Mammals don't and no other Mammals. But, for instance, let's say you later update Mammal to have a "feet" attribute:
class Mammal(object):
def __init__(self, name, feet):
self.name = name
self.feet = feet
Mammals now have a number of feet, and so you might expect Humans to also. But me.feet will throw an error, because the Human __init__ didn't initialize it, and the Mammal __init__ didn't run. Nor can you declare feet with me = Human('Joe', 2), because the Human __init__ doesn't take that argument. So you've created a maintenance problem -- Humans are now not really good Mammals, because some of their promised attributes are always undefined.
Using super avoids this problem:
class Human(Mammal):
def __init__(self, name):
super(Human, self).__init__(name, 2)
Of course, this requires you to subclass Human if you want a lot of pirates, but that's another problem.
The converse situation might be if you decided that most Mammals don't have names. Obviously, in this case, you would want to define name only in the Human __init__.
In the first example, you only say that Human inherits from Mammal ( the .__init__ on Mammal is not called), and if you try to use self.name you'll get an attribute error.
In the second example you are, in Human class, using the self.name from Mammal.

Categories

Resources