简体   繁体   English

Python 数据类继承与 class 变量

[英]Python dataclass inheritence with class variables

Consider the following example code考虑以下示例代码

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'

which prints 'y' and 'x' as expected.它按预期打印“y”和“x”。

Now I'd like to make x a ClassVar of type dict :现在我想让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

However, now the output is但是,现在 output 是

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

I'd expect that only ax gets modified and bx stays at the default init value `{'x': 'x'}.我希望只有ax被修改并且bx保持在默认的初始值 `{'x': 'x'}。

If the field would not be a ClassVar then I could use the default_factory=dict but that doesn't work in combination with ClassVar since it returns the error如果该字段不是ClassVar ,那么我可以使用default_factory=dict但这不能与ClassVar结合使用,因为它会返回错误

 Field cannot have a default factory

maybe using __post_init__ you can solve this也许使用__post_init__你可以解决这个问题

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

Class variables are shared between the parent and all child classes, so what you seem to want (a class variable that is declared in a parent, but child classes get their own copy they can manipulate) is conceptually impossible. Class 变量在父类和所有子类之间共享,因此您似乎想要的(在父类中声明的 class 变量,但子类获得自己可以操作的副本)在概念上是不可能的。

If you want to do it properly, you have to re-declare the class variable in every child:如果要正确执行此操作,则必须在每个子项中重新声明 class 变量:

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

Which now gives现在给

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

But if that is too cumbersome or impractical, I have this nifty footgun for you.但如果这太麻烦或不切实际,我有这把漂亮的脚枪给你。 Rather than using ClassVar s, it just programs your requirements explicitly into the base class as a function, and makes it look like an attribute with the @property decorator:它不是使用ClassVar s,而是将您的需求明确地编程到基础 class 作为 function,并使其看起来像带有@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

This correctly shares x only among child-class instances as well, but without you needing to write an additional line into each new child:这也仅在子类实例之间正确共享x ,但您不需要在每个新子实例中编写额外的行:

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

One caveat is that, unlike with ClassVar s, you can't call a property on the class without having an instance, eg Ax won't work.一个警告是,与ClassVar不同,您不能在没有实例的情况下调用 class 上的属性,例如Ax将不起作用。 But it seems you weren't trying to do that anyway.但似乎你并没有试图这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM