How to avoid parallel class hierarchy in python

前端 未结 3 1810
太阳男子
太阳男子 2021-01-21 04:19

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 concoc

相关标签:
3条回答
  • 2021-01-21 04:29

    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
    
    0 讨论(0)
  • 2021-01-21 04:30

    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')
    
    0 讨论(0)
  • 2021-01-21 04:46

    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']
    
    0 讨论(0)
提交回复
热议问题