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

问题描述 投票:0回答:3

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

暗示它已经为

__post_init__
运行了
NamedObj
,但没有为
NumberedObj
运行。 我想要的是让 NamedAndNumbered 为其两个混合类 Named 和 Numbered 运行
__post_init__
。有人可能会认为,如果
NamedAndNumbered
有一个像这样的
__post_init__
就可以做到:

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

但这只会给我一个错误

AttributeError: 'super' object has no attribute '__post_init__'
当我尝试调用
NamedObj.__post_init__()
.

在这一点上,我不完全确定这是数据类的错误/功能,还是与我对 Python 继承方法的可能有缺陷的理解有关。谁能伸出援手?

python multiple-inheritance mixins python-dataclasses method-resolution-order
3个回答
19
投票

这个:

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

不做你认为它做的事。

super(cls, obj)
将在 cls
type(obj).__mro__
 之后返回给班级
的代理 - 因此,在您的情况下,到
object
。合作
super()
电话的全部意义在于避免必须明确地打电话给每个父母。

合作

super()
呼叫的工作方式是,好吧,通过“合作”-IOW,mro 中的每个人都应该将呼叫转接到下一个班级(实际上,
super
名字是一个相当悲伤的名字选择,因为它不是关于调用“超类”,而是关于“调用 mro 中的下一个类”)。

IOW,您希望每个“可组合”数据类(不是混入 - 混入仅具有行为)来中继调用,因此您可以按任何顺序组合它们。第一个天真的实现看起来像:

@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 中的最后一个类(这里是

NamedObj
),mro 中的下一个类是内置的
object
类,它没有
__post_init__
方法。解决方案很简单:只需添加一个将此方法定义为 noop 的基类,并使所有可组合数据类都继承自它:

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

6
投票

问题(很可能)与

dataclass
es 无关。问题在于 Python 的method resolution。在
super()
上调用方法会调用 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

0
投票

解决方案: NamedObj 排在最后,在继承层次结构中没有任何继承。

@dataclass

NamedObj 类: 名称:str

def __post_init__(self):
    print("NamedObj __post_init__")
    self.name = "Name: " + self.name

@dataclass

类编号对象: 数字:整数 = 0

def __post_init__(self):
    super().__post_init__()
    print("NumberedObj __post_init__")
    self.number += 1

@dataclass

NamedAndNumbered 类(NumberedObj,NamedObj):

def __post_init__(self):
    super().__post_init__()
    print("NamedAndNumbered __post_init__")
© www.soinside.com 2019 - 2024. All rights reserved.