使用 python 数据类实现多重继承
Achieving multiple inheritance using python dataclasses
我正在尝试使用新的 python 数据类 来创建一些混合 类(在我写这篇文章时,我认为这听起来像是一个轻率的想法) ,我遇到了一些问题。看下面的例子:
from dataclasses import dataclass
@dataclass
class NamedObj:
name: str
def __post_init__(self):
print("NamedObj __post_init__")
self.name = "Name: " + self.name
@dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
print("NumberedObj __post_init__")
self.number += 1
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
如果我再尝试:
nandn = NamedAndNumbered('n_and_n')
print(nandn.name)
print(nandn.number)
我明白了
NumberedObj __post_init__
NamedAndNumbered __post_init__
n_and_n
1
建议 NamedObj
运行 __post_init__
,但 NumberedObj
没有。
我想要的是为其混合 类、命名和编号都设置 NamedAndNumbered 运行 __post_init__
。有人可能认为如果 NamedAndNumbered
有这样的 __post_init__
就可以做到:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
但是当我尝试调用 NamedObj.__post_init__()
.
时,这只会给我一个错误 AttributeError: 'super' object has no attribute '__post_init__'
在这一点上,我不完全确定这是一个 bug/feature 数据 类 还是与我对 Python 方法的理解可能存在缺陷有关遗产。谁能伸出援手?
问题(很可能)与 dataclass
es 无关。问题出在 Python 的 method resolution. Calling method on super()
invokes the first found method from parent class in the MRO 链中。因此,要使其正常工作,您需要手动调用父 类 的方法:
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
NamedObj.__post_init__(self)
NumberedObj.__post_init__(self)
print("NamedAndNumbered __post_init__")
另一种方法(如果你真的喜欢 super()
)可以通过在所有父 类 中调用 super()
来继续 MRO 链(但它需要有一个 __post_init__
在链中):
@dataclass
class MixinObj:
def __post_init__(self):
pass
@dataclass
class NamedObj(MixinObj):
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
@dataclass
class NumberedObj(MixinObj):
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
两种方法:
>>> nandn = NamedAndNumbered('n_and_n')
NamedObj __post_init__
NumberedObj __post_init__
NamedAndNumbered __post_init__
>>> print(nandn.name)
Name: n_and_n
>>> print(nandn.number)
1
这个:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
并不像您认为的那样。 super(cls, obj)
将 return class 在 cls
之后 type(obj).__mro__
的代理 - 所以,在你的情况下, object
。合作 super()
调用的全部意义在于避免必须显式调用每个 parents.
协作 super()
呼叫的工作方式是 "cooperative" - IOW,mro 中的每个人都应该将呼叫转接到下一个 class (实际上,super
这个名字是一个相当可悲的选择,因为它不是关于调用"the super class",而是关于"calling the next class in the mro")。
IOW,您希望每个 "composable" 数据classes(它们不是 mixins - mixins 只有行为)中继调用,因此您可以按任何顺序组合它们。第一个天真的实现看起来像:
@dataclass
class NamedObj:
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
@dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
但这不起作用,因为对于 mro 中的最后一个 class(此处 NamedObj
),mro 中的下一个 class 是内置的 object
class,它没有 __post_init__
方法。解决方案很简单:只需添加一个将此方法定义为 noop 的基础 class,并使所有可组合数据class 继承自它:
class Base(object):
def __post_init__(self):
# just intercept the __post_init__ calls so they
# aren't relayed to `object`
pass
@dataclass
class NamedObj(Base):
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
@dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
我正在尝试使用新的 python 数据类 来创建一些混合 类(在我写这篇文章时,我认为这听起来像是一个轻率的想法) ,我遇到了一些问题。看下面的例子:
from dataclasses import dataclass
@dataclass
class NamedObj:
name: str
def __post_init__(self):
print("NamedObj __post_init__")
self.name = "Name: " + self.name
@dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
print("NumberedObj __post_init__")
self.number += 1
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
如果我再尝试:
nandn = NamedAndNumbered('n_and_n')
print(nandn.name)
print(nandn.number)
我明白了
NumberedObj __post_init__
NamedAndNumbered __post_init__
n_and_n
1
建议 NamedObj
运行 __post_init__
,但 NumberedObj
没有。
我想要的是为其混合 类、命名和编号都设置 NamedAndNumbered 运行 __post_init__
。有人可能认为如果 NamedAndNumbered
有这样的 __post_init__
就可以做到:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
但是当我尝试调用 NamedObj.__post_init__()
.
AttributeError: 'super' object has no attribute '__post_init__'
在这一点上,我不完全确定这是一个 bug/feature 数据 类 还是与我对 Python 方法的理解可能存在缺陷有关遗产。谁能伸出援手?
问题(很可能)与 dataclass
es 无关。问题出在 Python 的 method resolution. Calling method on super()
invokes the first found method from parent class in the MRO 链中。因此,要使其正常工作,您需要手动调用父 类 的方法:
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
NamedObj.__post_init__(self)
NumberedObj.__post_init__(self)
print("NamedAndNumbered __post_init__")
另一种方法(如果你真的喜欢 super()
)可以通过在所有父 类 中调用 super()
来继续 MRO 链(但它需要有一个 __post_init__
在链中):
@dataclass
class MixinObj:
def __post_init__(self):
pass
@dataclass
class NamedObj(MixinObj):
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
@dataclass
class NumberedObj(MixinObj):
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
两种方法:
>>> nandn = NamedAndNumbered('n_and_n')
NamedObj __post_init__
NumberedObj __post_init__
NamedAndNumbered __post_init__
>>> print(nandn.name)
Name: n_and_n
>>> print(nandn.number)
1
这个:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
并不像您认为的那样。 super(cls, obj)
将 return class 在 cls
之后 type(obj).__mro__
的代理 - 所以,在你的情况下, object
。合作 super()
调用的全部意义在于避免必须显式调用每个 parents.
协作 super()
呼叫的工作方式是 "cooperative" - IOW,mro 中的每个人都应该将呼叫转接到下一个 class (实际上,super
这个名字是一个相当可悲的选择,因为它不是关于调用"the super class",而是关于"calling the next class in the mro")。
IOW,您希望每个 "composable" 数据classes(它们不是 mixins - mixins 只有行为)中继调用,因此您可以按任何顺序组合它们。第一个天真的实现看起来像:
@dataclass
class NamedObj:
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
@dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
但这不起作用,因为对于 mro 中的最后一个 class(此处 NamedObj
),mro 中的下一个 class 是内置的 object
class,它没有 __post_init__
方法。解决方案很简单:只需添加一个将此方法定义为 noop 的基础 class,并使所有可组合数据class 继承自它:
class Base(object):
def __post_init__(self):
# just intercept the __post_init__ calls so they
# aren't relayed to `object`
pass
@dataclass
class NamedObj(Base):
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
@dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
@dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")