如何使用具有多重继承的数据类特殊方法?


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}"我在网上做了一个搜索,我没有找到一个适当的解释和解决这个问题(可能应该搜索更好)。所以,我请求你们帮我找出解决这个问题的方法。

使用多重继承要求类通过在适当的位置调用它们的super()方法来进行协作。就像__init__应该服从super().__init__一样,__post_init__应该服从super() .__post_init__

由于dataclass没有公共基类,因此遵从super方法必须是防御性的;带no-op函数的getattr可以根据需要跳过超级调用。

@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类的__post_init__。但是由于dataclass通过代码生成而不是继承工作,所以超类是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:块来做到这一点,但有一种更简洁的方法。

内置的getattr函数允许获取属性或默认。我们可以使用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)()

相关内容

  • 没有找到相关文章

最新更新