抽象数据类的混合之间的冲突

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

1. 数据类混合问题已解决

为了制作在 mypy 下进行类型检查的抽象数据类,我将它们分为两个类,一类包含抽象方法,一类包含数据成员,如这个答案中所述。抽象类继承自数据类。但是,当另一个抽象类和数据类对从第一个抽象类和数据类对继承时,这会遇到问题:“祖先”数据类的字段被“后代”擦除。例如:

from dataclasses import dataclass
from abc import ABC, abstractmethod

@dataclass
class ADataclassMixin:
    a_field: int = 1

class A(ADataclassMixin, ABC):
    @abstractmethod
    def method(self):
        pass

@dataclass
#class BDataclassMixin(A):  # works  but fails mypy 0.931 type-check
class BDataclassMixin:  # fails
    b_field: int = 2
    pass

class B(BDataclassMixin, A):
    def method(self):
        return self

o = B(a_field=5)

最后一行失败,产生此错误消息:

TypeError: BDataclassMixin.__init__() got an unexpected keyword argument 'a_field'
正如预期的那样,

B 的方法解析顺序 (

B.__mro__
) 是
(B, BDataclassMixin, A, ADataclassMixin, ABC, object)
。但是找不到
a_field

如上面注释掉的行所示,一个解决方案是将祖先类显式放在后代数据类的声明中:

class BDataclassMixin(A)
而不是
class BDataclassMixin
。但这会导致类型检查失败,因为数据类只能是具体类。

2 该解决方案存在问题,未解决

如果我们添加第三个类(继承自 B),上述解决方案就会崩溃:

@dataclass
#class CDataclassMixin:  # fails
class CDataclassMixin(A):  # fails
#class CDataclassMixin(B, A):  # works  but fails type-check
    c_field: int = 3
    pass

class C(CDataclassMixin, B):
    def method(self):
        return "C's result"
    pass

o = C(b_field=5)

现在,C 拥有

a_field
c_field
,但失去了
b_field

我发现,如果我显式声明 CDataclassMixin 从 B 和 A 继承(按顺序),

b_field
将与
a_field_ and 
c_field` 一起出现在结果类中。然而,在每个混入中明确声明继承层次结构违背了混入的目的,即能够独立于所有其他混入对它们进行编码,并以您喜欢的任何方式轻松地混合它们。

制作抽象数据类混合的正确方法是什么,以便从它们继承的类包含所有数据类字段?

python abstract-class mypy python-typing python-dataclasses
2个回答
4
投票

正确的解决方案是放弃 DataclassMixin 类,而简单地将抽象类变成数据类,如下所示:

@dataclass  # type: ignore[misc]
class A(ABC):
    a_field: int = 1

    @abstractmethod
    def method(self):
        pass

@dataclass  # type: ignore[misc]
class B(A):
    b_field: int = 2

@dataclass
class C(B):
    c_field: int = 3

    def method(self):
        return self

失败的原因是,正如有关数据类的文档中所述,数据类中的完整字段集是在数据类编译时确定的,而不是在继承数据类时确定。生成数据类的 __init__

 函数的内部代码只能检查数据类本身声明的 MRO,而不能在混合到另一个类中时检查。

有必要将

# type: ignore[misc]

 添加到每个抽象数据类的 
@dataclass
 行,不是因为解决方案是错误的,而是因为 
mypy 是错误的。需要数据类具体的是 mypy,,而不是 Python。正如 ilevkivskyi 在 mypy 第 5374 期 中所解释的那样,问题是 mypy 希望数据类成为 Type
 对象 
并且 每个 Type
 对象都能够被实例化。这是一个已知问题,等待解决。

问题和解决方案中的行为正是数据类应该如何表现。而且,令人高兴的是,以这种方式(普通方式)继承的抽象数据类可以随意混合到其他类中,与其他混合没有什么不同。


1
投票
将 mixin 作为最后一个基类可以正常工作,不会出现错误:

@dataclass class ADataclassMixin: a_field: int = 1 class A(ABC, ADataclassMixin): @abstractmethod def method(self): pass @dataclass class BDataclassMixin: b_field: int = 2 class B(A, BDataclassMixin): def method(self): return self o = B(a_field=5) print((o.a_field, o.b_field)) # (5,2)
    
© www.soinside.com 2019 - 2024. All rights reserved.