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

Python dataclasses.dataclass reference to variable instead of instance variable

c1 和 c2 的构造函数中的默认值应该为 b 和 b 生成新的实例变量。相反,c1.a 和 c2.a 似乎引用了同一个变量。 @dataclass 是在创建一个 class 变量吗?这似乎与预期的功能不一致,我在文档中找不到任何关于 class 变量的信息。所以,我认为这是一个错误。有人可以向我解释如何解决吗?我应该将其报告为 python 跟踪器上的错误吗?

我知道这个问题一定与 python 通过引用传递对象和通过值传递内置类型的方式有关,因为 b 属性(只是一个浮点数)显示了 expected/desired 行为而 a 属性(这是一个用户定义的对象)只是一个参考。

谢谢!

从数据classes导入数据class

"""输入"""

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

感谢 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() 构造函数中(请注意,如果我不传递深度复制,我会遇到与上述相同的问题)。这是我的最终代码片段和输出:

"""代码"""

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

"""输出"""

容器(a=VS(v=2, s=1), b=1)

容器(a=VS(v=2, s=1), b=1)

容器(a=VS(v=0, s=1), b=0)

容器(a=VS(v=2, s=1), b=1)

在 OP 的原始示例中,当定义 Container class 时,将创建一个 VS 对象。然后该对象在 Container class 的所有实例之间共享。这是一个问题,因为 user-defined class 等 VS 会导致可变对象。因此,更改任何 Container 对象中的 a 将更改所有其他 Container 对象中的 a

你想在每次 Container class 在初始化时实例化时生成一个新的 VS 对象。为此,使用 field 函数的 default_factory 是一个很好的方法。传递 lambda 函数允许所有这些内联完成。

我在容器中添加了一个 c 成员变量和另一个 VS class 来说明这样做时成员是独立的。

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