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

2024-05-18 02:12:29 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试使用新的python数据类来创建一些混合类(在我写这篇文章时,我认为这听起来像是一个鲁莽的想法),但我遇到了一些问题。请看下面的例子:

从数据类导入数据类

@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运行。 我想要的是为它的混合类命名并编号运行__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/特性,还是与我对Python继承方法的理解可能存在缺陷有关。谁能帮帮忙吗


Tags: 数据nameselfnumberinitdefpostclass
2条回答

这:

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

不会做你认为它会做的事super(cls, obj)将在type(obj).__mro__中的cls之后返回类的代理-因此,在您的情况下,将返回到object。合作super()调用的全部要点是避免显式地调用每一个家长

协作super()调用的工作方式是,好的,通过“协作”-IOW,mro中的每个人都应该将调用转发到下一个类(实际上,super名称是一个相当令人伤心的选择,因为它不是关于调用“超级类”,而是关于“调用mro中的下一个类”)

注意,您希望每个“可组合”数据类(不是mixin-mixin只具有行为)中继调用,因此您可以按任意顺序组合它们。第一个简单的实现如下所示:

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

问题(很可能)与dataclass无关。问题在于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

相关问题 更多 >