使用 python 数据类实现多重继承

Achieving multiple inheritance using python dataclasses

我正在尝试使用新的 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

建议 NamedObj 运行 __post_init__,但 NumberedObj 没有。 我想要的是为其混合 类、命名和编号都设置 NamedAndNumbered 运行 __post_init__。有人可能认为如果 NamedAndNumbered 有这样的 __post_init__ 就可以做到:

def __post_init__(self):
    super(NamedObj, self).__post_init__()
    super(NumberedObj, self).__post_init__()
    print("NamedAndNumbered __post_init__")

但是当我尝试调用 NamedObj.__post_init__().

时,这只会给我一个错误 AttributeError: 'super' object has no attribute '__post_init__'

在这一点上,我不完全确定这是一个 bug/feature 数据 类 还是与我对 Python 方法的理解可能存在缺陷有关遗产。谁能伸出援手?

问题(很可能)与 dataclasses 无关。问题出在 Python 的 method resolution. Calling method on super() invokes the first found method from parent class in the 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

这个:

def __post_init__(self):
    super(NamedObj, self).__post_init__()
    super(NumberedObj, self).__post_init__()
    print("NamedAndNumbered __post_init__")

并不像您认为的那样。 super(cls, obj) 将 return class cls 之后 type(obj).__mro__ 的代理 - 所以,在你的情况下, object。合作 super() 调用的全部意义在于避免必须显式调用每个 parents.

协作 super() 呼叫的工作方式是 "cooperative" - IOW,mro 中的每个人都应该将呼叫转接到下一个 class (实际上,super这个名字是一个相当可悲的选择,因为它不是关于调用"the super class",而是关于"calling the next class in the mro")。

IOW,您希望每个 "composable" 数据classes(它们不是 mixins - mixins 只有行为)中继调用,因此您可以按任何顺序组合它们。第一个天真的实现看起来像:

@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 中的最后一个 class(此处 NamedObj),mro 中的下一个 class 是内置的 object class,它没有 __post_init__ 方法。解决方案很简单:只需添加一个将此方法定义为 noop 的基础 class,并使所有可组合数据class 继承自它:

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:
    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__")