如何避免 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 并具有自己的继承。
从设计的角度来看,我可以看到第一个问题是 habits
和 type
应该是 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']
我最近 运行 在我的 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 并具有自己的继承。
从设计的角度来看,我可以看到第一个问题是 habits
和 type
应该是 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']