【问题标题】:How to avoid parallel class hierarchy in python如何避免python中的并行类层次结构
【发布时间】:2020-06-02 14:06:50
【问题描述】:

我最近在我的python 代码中遇到了一种奇怪的小气味,我认为这与并行继承有关。这是我编造的一个小例子:

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()

我有一个通用的狗类,具体的狗实现继承自该类。每个狗类(包括通用/抽象类)都有一组习惯,它们本身由习惯的另一个类层次结构表示。

我对我必须始终拥有完全相同的两个层次结构这一事实感到恼火。此外,DogHabits 之间的继承在习惯层次结构中很有用,但在狗层次结构中没有用,因为我需要为狗层次结构中的每个类实例化一个单独的习惯对象。

解决这个问题的方法是什么?我可能想添加很多狗类的实现,更新相应的习惯层次结构听起来很乏味,而且闻起来很糟糕......

【问题讨论】:

  • 这不是python问题。这是设计气味而不是代码气味。根本缺陷是您将设计和编码结合在一起。回到设计阶段,写一些 UML 并在编写任何代码之前解决问题。
  • 好点....但不幸的是现在代码已经写好了
  • "但不幸的是现在代码已经写好了" - 没有任何区别。您仍然可以返回并正确设计它。
  • @nicomp 这就是我试图通过提出这个问题并从人们那里获得反馈的目的。请不要在这里绕圈子。
  • @JLPeyret 不,用于设计的 UML 类图不是一种气味。

标签: python oop diamond-problem


【解决方案1】:

这可能太过分了,但我认为不需要单独的 DogHabits 类。 habits 应该是类属性,而不是实例属性,可以由__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 本身也更像是类属性而不是实例属性,因为它只是已经由类本身编码的信息的(替代)字符串表示。由于您不会附加到现有值,因此在必要时设置类属性比通过__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

【讨论】:

  • 你的回答很有趣。我会试一试,让你知道它是否适用于我的实际用例......
  • 确保您使用的是更新后的答案; (使用cls.habits = cls.habits + habits),因为以前的版本总是更新Dog.habits,而不是为每个子类创建一个新列表。
【解决方案2】:

IF习惯需要一个类属性,而不是实例属性,这可能实际上是元类的好用处。

习惯不一定是一个简单的列表,它可以是别的东西,只要有加法和返回新的概念。 (__add____radd__ 在 Habits 课程上可以达到我认为的效果)

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)

输出:

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']

【讨论】:

  • 这是一个创意/优雅的解决方案,但在我的用例中,我的 Dog 恰好也是一个 ABC。如果我也尝试将其设置为元类,则会收到错误消息:TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases 你将如何解决?
  • 您的解决方案似乎适用于我的用例的简化版本,我非常有信心它将适用于最终用例。因此,我会接受它。
【解决方案3】:

这个答案假设 DogHabits 比单纯的列表复杂得多,并且确实值得一个具有自己继承的专用类。

从设计的角度来看,我可以看到第一个问题是habitstype 应该是类成员还是实例成员。同样,此答案假定有理由使它们成为实例成员。

我会让Habits 成为Dogs 的内部类,并在类文档中声明可以通过在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')

【讨论】:

  • 感谢您的努力,但是嵌套类让我很不舒服...我认为具有并行层次结构更好...
猜你喜欢
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
  • 2016-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多