【问题标题】:How to uses dataclass special methods with multiple inheritance?如何使用具有多重继承的数据类特殊方法?
【发布时间】:2021-11-07 04:41:54
【问题描述】:
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,即super().__post_init__

标签: python class inheritance multiple-inheritance


【解决方案1】:

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

由于dataclasses 没有公共基类,因此必须遵守super 方法; getattr 带有 no-op 功能,可以根据需要跳过 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 通过代码生成而不是继承工作,超类是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 允许获取属性或默认值。代替a.b,我们可以使用getattr(a, "b", default)。由于我们要调用一个方法,因此一个有用的默认值是一个什么都不做的可调用对象。

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

【讨论】:

  • 感谢您的详细回复。我是 OOP 的新手,所以现在对我来说有点复杂。但是,看来,我开始变得更好了。谢谢你。
  • 哦,其实我还有一个问题。我以为我可以弄清楚getattr(super(), "__post_init__", lambda: None)() 是如何工作的,但我仍然不明白:(。我没有找到 getattr(...)() 的一些例子。第二对括号真的让我很困惑。虽然,我发现当我删除这个括号时,似乎整个字符串不再有意义。另外 lambda: None 也让我有点困惑。如果我要求一些明显的事情来解释,我很抱歉,但是我对所有这些东西都很陌生,我真的找不到一些明确的解释,所以我希望你能帮助我
  • @Jeffery-the-newbie 这确实有点棘手。我稍后会展开答案。
  • 当然,我会期待的。
  • 哇,多么神奇的解释:非常冷静,清晰的逐步解释!我希望我能超过 1 次支持你的答案。不管怎样,你做得很好。我非常感谢这样的人可以解释一些复杂的事情,至少对我来说,事情甚至是我根本不知道的事情。再一次,这是对你的精彩、伟大的解释链。谢谢。
猜你喜欢
  • 2013-09-01
  • 2016-10-12
  • 2019-03-16
  • 2011-07-29
  • 1970-01-01
  • 2020-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多