【问题标题】:Python dataclasses.dataclass reference to variable instead of instance variablePython dataclasses.dataclass 引用变量而不是实例变量
【发布时间】:2020-11-01 07:06:52
【问题描述】:

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

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

谢谢!

从数据类导入数据类

"""输入"""

@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


    【解决方案1】:

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

    您希望每次在初始化时实例化 Container 类时都生成一个新的 VS 对象。为此,使用field 函数的default_factory 是一个很好的方法。传递一个 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 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))
    

    【讨论】:

      【解决方案2】:

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

      """代码"""

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

      【讨论】:

      • 看看我的回答能不能解决你的问题。 python 的可变参数“特性”可能是我最不喜欢的,尽管我了解它或多或少地简化了语言的设计。
      • 喜欢这个解决方案
      • 顺便提一下,如果您喜欢我在stackoverflow.com/a/63818048/1361752 中给出的解决方案,最好接受它作为“正确”答案。这可以帮助人们找到它并给我信用 Stack Exchanges 功绩系统。 (我的功劳并不大,我得到的比我需要的要多,但这对某些人来说确实很重要,尤其是如果他们刚开始)
      • 谢谢迦勒!我以前不知道如何接受答案。刚刚做到了。
      猜你喜欢
      • 1970-01-01
      • 2010-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-31
      相关资源
      最近更新 更多