如何使用具有多重继承的数据类特殊方法?
How to uses dataclass special methods with multiple inheritance?
from dataclasses import dataclass, field
from typing import Dict
@dataclass
class A:
a: Dict[str, int] = field(default_factory=dict)
def __post_init__(self):
self.a = {'a1': 0, 'a2': 0}
def add_key_a(self, key):
self.a['key'] = 0
@dataclass
class B:
b: Dict[str, int] = field(default_factory=dict)
def __post_init__(self):
self.b = {'b1': 0, 'b2': 0}
def add_key_b(self, key):
self.b['key'] = 0
@dataclass
class C(A, B):
pass
user = C()
print(user)
# C(b={}, a={'a1': 0, 'a2': 0})
我得到一个空的 'b' 字典,但预期会得到“{'b1': 0, 'b2': 0}”。
我在 Internet 上进行了搜索,但没有找到对这个问题的正确解释和解决方案(可能应该搜索得更好)。所以,请大家帮我看看如何解决这个问题。
使用多重继承需要 classes 通过在适当的地方调用它们的 super()
方法来协作。就像 __init__
应该服从 super().__init__
,__post_init__
应该服从 super() .__post_init__
.
由于 dataclass
es 没有共同的基础class,因此遵循 super
方法必须是防御性的; getattr
可以使用空操作函数来根据需要跳过 super 调用。
@dataclass
class A:
a: Dict[str, int] = field(default_factory=dict)
def __post_init__(self):
getattr(super(), "__post_init__", lambda: None)()
self.a = {'a1': 0, 'a2': 0}
def add_key_a(self, key):
self.a['key'] = 0
@dataclass
class B:
b: Dict[str, int] = field(default_factory=dict)
def __post_init__(self):
getattr(super(), "__post_init__", lambda: None)()
self.b = {'b1': 0, 'b2': 0}
def add_key_b(self, key):
self.b['key'] = 0
天真地,人们会用 super().__post_init__()
调用 super
class 的 __post_init__
。但是由于 dataclass
通过代码生成而不是继承来工作,超级 class 是 object
——它没有 __post_init__
方法!因此,最终查找将失败:
>>> c = C()
>>> super(C, c).__post_init__ # initial __post_init__ used by C instances
<bound method A.__post_init__ of C(b={}, a={'a1': 0, 'a2': 0})>
>>> super(A, c).__post_init__ # second __post_init__ used by C
<bound method B.__post_init__ of C(b={}, a={'a1': 0, 'a2': 0})>
>>> super(B, c).__post_init__ # final __post_init__ used by C
...
AttributeError: 'super' object has no attribute '__post_init__'
解决这个问题的方法很简单:只要抓住 AttributeError
如果它发生了,在这种情况下什么也不做。我们可以用 try:
except:
块来做到这一点,但有一种更简洁的方法。
The builtin getattr
function 允许获取属性 或默认值 。我们可以使用 getattr(a, "b", default)
而不是 a.b
。由于我们要调用一个方法,因此有用的默认值是一个什么都不做的可调用对象。
>>> lambda : None # callable that does nothing
<function __main__.<lambda>()>
>>> # definition | call
>>> (lambda: None)() # calling does nothing
>>> # getattr fetches attribute/method...
>>> getattr(super(A, c), "__post_init__")
<bound method B.__post_init__ of C(b={}, a={'a1': 0, 'a2': 0})>
>>> # ... and can handle a default
>>> getattr(super(B, c), "__post_init__", lambda: None)
<function __main__.<lambda>()>
将其付诸实践,我们将 ….__post_init__
替换为 getattr
。值得注意的是,正如我们在 ….__post_init__
查找之后需要 ()
进行调用一样,在 getattr
查找之后我们仍然需要 ()
进行调用。
super().__post_init__()
#super | method | call
# |super | | method | | default | | call
getattr(super(), "__post_init__", lambda: None)()
from dataclasses import dataclass, field
from typing import Dict
@dataclass
class A:
a: Dict[str, int] = field(default_factory=dict)
def __post_init__(self):
self.a = {'a1': 0, 'a2': 0}
def add_key_a(self, key):
self.a['key'] = 0
@dataclass
class B:
b: Dict[str, int] = field(default_factory=dict)
def __post_init__(self):
self.b = {'b1': 0, 'b2': 0}
def add_key_b(self, key):
self.b['key'] = 0
@dataclass
class C(A, B):
pass
user = C()
print(user)
# C(b={}, a={'a1': 0, 'a2': 0})
我得到一个空的 'b' 字典,但预期会得到“{'b1': 0, 'b2': 0}”。 我在 Internet 上进行了搜索,但没有找到对这个问题的正确解释和解决方案(可能应该搜索得更好)。所以,请大家帮我看看如何解决这个问题。
使用多重继承需要 classes 通过在适当的地方调用它们的 super()
方法来协作。就像 __init__
应该服从 super().__init__
,__post_init__
应该服从 super() .__post_init__
.
由于 dataclass
es 没有共同的基础class,因此遵循 super
方法必须是防御性的; getattr
可以使用空操作函数来根据需要跳过 super 调用。
@dataclass
class A:
a: Dict[str, int] = field(default_factory=dict)
def __post_init__(self):
getattr(super(), "__post_init__", lambda: None)()
self.a = {'a1': 0, 'a2': 0}
def add_key_a(self, key):
self.a['key'] = 0
@dataclass
class B:
b: Dict[str, int] = field(default_factory=dict)
def __post_init__(self):
getattr(super(), "__post_init__", lambda: None)()
self.b = {'b1': 0, 'b2': 0}
def add_key_b(self, key):
self.b['key'] = 0
天真地,人们会用 super().__post_init__()
调用 super
class 的 __post_init__
。但是由于 dataclass
通过代码生成而不是继承来工作,超级 class 是 object
——它没有 __post_init__
方法!因此,最终查找将失败:
>>> c = C()
>>> super(C, c).__post_init__ # initial __post_init__ used by C instances
<bound method A.__post_init__ of C(b={}, a={'a1': 0, 'a2': 0})>
>>> super(A, c).__post_init__ # second __post_init__ used by C
<bound method B.__post_init__ of C(b={}, a={'a1': 0, 'a2': 0})>
>>> super(B, c).__post_init__ # final __post_init__ used by C
...
AttributeError: 'super' object has no attribute '__post_init__'
解决这个问题的方法很简单:只要抓住 AttributeError
如果它发生了,在这种情况下什么也不做。我们可以用 try:
except:
块来做到这一点,但有一种更简洁的方法。
The builtin getattr
function 允许获取属性 或默认值 。我们可以使用 getattr(a, "b", default)
而不是 a.b
。由于我们要调用一个方法,因此有用的默认值是一个什么都不做的可调用对象。
>>> lambda : None # callable that does nothing
<function __main__.<lambda>()>
>>> # definition | call
>>> (lambda: None)() # calling does nothing
>>> # getattr fetches attribute/method...
>>> getattr(super(A, c), "__post_init__")
<bound method B.__post_init__ of C(b={}, a={'a1': 0, 'a2': 0})>
>>> # ... and can handle a default
>>> getattr(super(B, c), "__post_init__", lambda: None)
<function __main__.<lambda>()>
将其付诸实践,我们将 ….__post_init__
替换为 getattr
。值得注意的是,正如我们在 ….__post_init__
查找之后需要 ()
进行调用一样,在 getattr
查找之后我们仍然需要 ()
进行调用。
super().__post_init__()
#super | method | call
# |super | | method | | default | | call
getattr(super(), "__post_init__", lambda: None)()