如何避免 python 中的并行 class 层次结构

How to avoid parallel class hierarchy in python

我最近 运行 在我的 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()

我有一只普通的狗 class,具体的狗实现继承自它。每只狗 class(包括 generic/abstract 一只)都有一套习惯,它们本身由另一个 class 习惯层次结构代表。

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

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

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

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

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

我会让 Habits 成为 Dogs 的内部 class 并在 class 文档中说明可以通过构建子 class 来自定义它在 Dogs 的子 class 中:

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

IF习惯需要一个class属性,而不是实例属性,这个可能其实很好用元classes.

习惯不一定是一个简单的列表,它可以是别的东西,只要有加前和return新的概念即可。 (我认为 __add____radd__ 上的习惯 class 可以解决问题)

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