Python dataclasses.dataclass 引用变量而不是实例变量

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

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)
python instance-variables class-variables
2个回答
8
投票

在 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))

0
投票

感谢 Eric S 提供解释:

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)
© www.soinside.com 2019 - 2024. All rights reserved.