c1
和c2
构造函数中的默认值应该为b
和b
生成新的实例变量。相反,看起来 c1.a
和 c2.a
引用了同一个变量。 @dataclass
正在创建类变量吗?这似乎与预期的功能不一致,并且我在文档中找不到有关类变量的任何信息。所以,我认为这是一个错误。有人可以向我解释如何解决它吗?我应该将其报告为 python 跟踪器上的错误吗?
我知道这个问题一定与Python通过引用传递对象和通过值传递内置类型的方式有关,因为
b
属性(只是一个浮点数)显示了预期/期望的行为,而a
属性(这是一个用户定义的对象)只是一个参考。
谢谢!
from dataclasses import dataclass
@dataclass
class VS:
v: float # value
s: float # scale factor
def scaled_value(self):
return self.v*self.s
@dataclass
class Container:
a: VS = VS(1, 1)
b: float = 1
c1 = Container()
c2 = Container()
print(c1)
print(c2)
c1.a.v = -999
c1.b = -999
print(c1)
print(c2)
Container(a=VS(v=1, s=1), b=1)
Container(a=VS(v=1, s=1), b=1)
Container(a=VS(v=-999, s=1), b=-999)
Container(a=VS(v=-999, s=1), b=1)
在 OP 的原始示例中,定义
VS
类时会创建单个 Container
对象。然后该对象在 Container
类的所有实例之间共享。这是一个问题,因为用户定义的类(例如 VS
)会导致可变对象。因此,更改任何 a
对象中的 Container
都会更改所有其他 a
对象中的 Container
您希望在初始化时每次实例化
Container
类时生成一个新的 VS 对象。为此,使用 default_factory
函数的 field
是一个好方法。传递 lambda 函数允许所有这些内联完成。
我用另一个
c
类向容器添加了一个 VS
成员变量,以说明这样做时成员是独立的。
from dataclasses import dataclass, field
@dataclass
class VS:
v: float # value
s: float # scale factor
def scaled_value(self):
return self.v*self.s
# Use a zero-argument lambda function for default_factory argument of field function.
@dataclass
class Container:
a: VS = field(default_factory=lambda:VS(1,1))
b: float = 1
c: VS = field(default_factory=lambda:VS(1,2))
c1 = Container()
c2 = Container()
print(c1)
print(c2)
c1.a.v = -999
c1.c.s = -999
print(c1)
print(c2)
输出:
Container(a=VS(v=1, s=1), b=1, c=VS(v=1, s=2))
Container(a=VS(v=1, s=1), b=1, c=VS(v=1, s=2))
Container(a=VS(v=-999, s=1), b=1, c=VS(v=1, s=-999))
Container(a=VS(v=1, s=1), b=1, c=VS(v=1, s=2))
c1 和 c2 共享 a 的同一个实例。这是可变默认参数问题:https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments 使用default_factory为每个容器创建一个新的VS。
default_factory 不允许我为多个属性拥有一组唯一的默认 VS 值,因为 VS 默认值需要在 VS 数据类中定义。例如,如果我希望 a 默认为 VS(1,1) 但我希望 b 默认为 VS(1,2),则 default_factory 对我没有帮助。因此,我找到了解决方法,即创建关键字条目的字典并将深层副本传递到我的 Container() 构造函数中(注意,如果我不传递深层副本,我会遇到与上面相同的问题)。这是我的最终代码片段和输出:
from dataclasses import dataclass, field
from copy import deepcopy
@dataclass
class VS:
v: float = 1 # value
s: float = 1 # scale factor
def scaled_value(self):
return self.v*self.s
@dataclass
class Container:
a: VS = field(default_factory=VS)
b: float = 1
ip = {'a':VS(2,1),'b':1}
c1 = Container(**deepcopy(ip))
c2 = Container(**deepcopy(ip))
print(c1)
print(c2)
c1.a.v = 0
c1.b = 0
print(c1)
print(c2)
Container(a=VS(v=2, s=1), b=1)
Container(a=VS(v=2, s=1), b=1)
Container(a=VS(v=0, s=1), b=0)
Container(a=VS(v=2, s=1), b=1)