带有类变量的Python数据类继承



考虑以下示例代码

from dataclasses import dataclass, field
from typing import ClassVar

@dataclass
class Base:
x: str = field(default='x', init=False)

@dataclass
class A(Base):
name: str

@dataclass
class B(Base):
name: str

a = A('test_a')
b = B('test_b')
a.x = 'y'
a.x  # prints 'y'
b.x  # prints 'x'

它按预期打印"y"one_answers"x"。

现在我想使x成为dict:类型的ClassVar

from dataclasses import dataclass, field
from typing import ClassVar, Dict

@dataclass
class Base:
x: ClassVar[Dict[str, str]] = field(default={'x': 'x'}, init=False)

@dataclass
class A(Base):
name: str

@dataclass
class B(Base):
name: str

a = A('test_a')
b = B('test_b')
a.x['y'] = 'y'
a.x
b.x

然而,现在的输出是

a.x => {'x': 'x', 'y': 'y'}
b.x => {'x': 'x', 'y': 'y'}

我希望只有a.x被修改,而b.x保持在默认的初始值"{‘x’:‘x’}"。

如果字段不是ClassVar,那么我可以使用default_factory=dict,但这与ClassVar组合不起作用,因为它返回错误

Field cannot have a default factory

类变量在父类和所有子类之间共享,因此您想要的(在父类中声明的类变量,但子类有自己的副本可以操作(在概念上是不可能的。

如果你想正确地执行它,你必须在每个子级中重新声明类变量:

from dataclasses import dataclass
from typing import ClassVar, Dict

@dataclass
class Base:
x: ClassVar[Dict[str, str]] = {'x': 'x'}

@dataclass
class A(Base):
x: ClassVar[Dict[str, str]] = {'x': 'x'}
name: str

@dataclass
class B(Base):
x: ClassVar[Dict[str, str]] = {'x': 'x'}
name: str
a = A('test_a')
b = B('test_b')
a.x['y'] = 'y'
a.x
b.x

现在给出

a.x => {'x': 'x', 'y': 'y'}
b.x => {'x': 'x'}

但如果这太麻烦或不切实际,我有这个漂亮的脚凳给你。它不是使用ClassVar,而是将您的需求作为一个函数显式地编程到基类中,并使其看起来像@property装饰器的属性:

from dataclasses import dataclass
from typing import Dict

@dataclass
class Base:
@property
def x(self) -> Dict[str, str]:
cls = type(self)
# first call per child class instance will initialize a proxy
if not hasattr(cls, "_x"):
setattr(cls, "_x", {"x": "x"})  # store the actual state of "x" in "_x"
return getattr(cls, "_x")

@dataclass
class A(Base):
name: str

@dataclass
class B(Base):
name: str

a_1 = A('test_a_1')
a_2 = A('test_a_2')
b = B('test_b')
a_1.x['y'] = 'y'
a_1.x
a_2.x
b.x

这也只在子类实例之间正确地共享x,但不需要在每个新的子类中写入额外的行:

a.x => {'x': 'x', 'y': 'y'}
a_1.x => {'x': 'x', 'y': 'y'}
b.x => {'x': 'x'}

需要注意的一点是,与ClassVar不同,如果没有实例,就不能调用类的属性,例如A.x不起作用。但你似乎并没有这么做。

也许使用__post_init__可以解决这个

@dataclass
class Base:
# x: ClassVar[Dict[str, str]] = field(default=dict(val), init=False)
def __post_init__(self) :
self.x = {'x':'x'}

最新更新