【问题标题】:Python dataclass inheritance with class variablesPython数据类继承与类变量
【发布时间】:2021-04-10 03:36:54
【问题描述】:

考虑以下示例代码

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”和“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

【问题讨论】:

  • default={'x': 'x'} 表示所有使用默认值的实例共享对同一个字典的引用。如果你不能使用default_factory,这似乎是不可避免的。
  • @jonrsharpe 更新我使用default_factory 得到的错误,它似乎不兼容

标签: python inheritance class-variables python-dataclasses


【解决方案1】:

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

如果你想正确地做到这一点,你必须在每个孩子中重新声明类变量:

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'}

但如果这太麻烦或不切实际,我有这把漂亮的脚枪给你。它没有使用ClassVars,而是将您的需求作为函数显式编程到基类中,并使其看起来像带有@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'}

需要注意的是,与 ClassVars 不同,您不能在没有实例的情况下调用类的属性,例如A.x 不起作用。但似乎你并没有尝试这样做。

【讨论】:

    【解决方案2】:

    也许使用__post_init__你可以解决这个问题

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

    【讨论】:

      猜你喜欢
      • 2013-02-11
      • 2018-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-19
      • 2015-12-09
      • 2011-07-30
      • 2012-11-04
      相关资源
      最近更新 更多