我正在尝试使用新的 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
暗示它已经为
__post_init__
运行了NamedObj
,但没有为NumberedObj
运行。
我想要的是让 NamedAndNumbered 为其两个混合类 Named 和 Numbered 运行 __post_init__
。有人可能会认为,如果NamedAndNumbered
有一个像这样的__post_init__
就可以做到:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
但这只会给我一个错误
AttributeError: 'super' object has no attribute '__post_init__'
当我尝试调用NamedObj.__post_init__()
.
在这一点上,我不完全确定这是数据类的错误/功能,还是与我对 Python 继承方法的可能有缺陷的理解有关。谁能伸出援手?
这个:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
不做你认为它做的事。
super(cls, obj)
将在 cls
type(obj).__mro__
之后返回给班级的代理 - 因此,在您的情况下,到
object
。合作super()
电话的全部意义在于避免必须明确地打电话给每个父母。
合作
super()
呼叫的工作方式是,好吧,通过“合作”-IOW,mro 中的每个人都应该将呼叫转接到下一个班级(实际上,super
名字是一个相当悲伤的名字选择,因为它不是关于调用“超类”,而是关于“调用 mro 中的下一个类”)。
IOW,您希望每个“可组合”数据类(不是混入 - 混入仅具有行为)来中继调用,因此您可以按任何顺序组合它们。第一个天真的实现看起来像:
@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 中的最后一个类(这里是
NamedObj
),mro 中的下一个类是内置的object
类,它没有__post_init__
方法。解决方案很简单:只需添加一个将此方法定义为 noop 的基类,并使所有可组合数据类都继承自它:
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(Base):
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__")
问题(很可能)与
dataclass
es 无关。问题在于 Python 的method resolution。在 super()
上调用方法会调用 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
解决方案: NamedObj 排在最后,在继承层次结构中没有任何继承。
@dataclass
NamedObj 类: 名称:str
def __post_init__(self):
print("NamedObj __post_init__")
self.name = "Name: " + self.name
@dataclass
类编号对象: 数字:整数 = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
@dataclass
NamedAndNumbered 类(NumberedObj,NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")