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

标签 python class inheritance 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}”。 我在互联网上进行了搜索,但没有找到该问题的正确解释和解决方案(可能应该搜索更好)。所以,我请求你们帮我找出解决这个问题的方法。

最佳答案

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

由于dataclass没有共同的基类,因此遵循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 类的 __post_init__ 。但由于 dataclass 通过代码生成而不是继承来工作,因此父类(super 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: block 来做到这一点,但有一种更简洁的方法。

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)()

关于python - 如何使用具有多重继承的数据类特殊方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69136413/

相关文章:

Equals() 的 C++ 双重分派(dispatch)

javascript - Javascript 中可以继承模块吗?

python - Gensim Word2Vec 令人疲惫不堪的可迭代

python - python中的 "append"和 "+"有什么区别?

c++ - 动态函数内存? C++

c# - 有没有办法对列表中的 Color 元素进行排序(c#)?

oop - 深度继承层次结构的替代方案?

python - 如何从数据存储区刷新 NDB 实体?

python - 在 Python 中使用 Stanford Tregex

java - 将新歌曲添加到专辑 java