具有复杂结构的数据类实例的完整副本

Rom*_*Zh. 9 python python-3.x python-dataclasses

我想创建数据类现有实例的副本并对其进行修改。

假设我们有一个数据类和该数据类的一个实例:

from dataclasses import dataclass, field, InitVar, replace

@dataclass
class D:
    a: float = 10.                # Normal attribute with a default value
    b: InitVar[float] = 20.       # init-only attribute with a default value 
    c: float = field(init=False)  # an attribute that will be defined in __post_init__
    
    def __post_init__(self, b):
        self.c = self.a + b

d1 = D()
Run Code Online (Sandbox Code Playgroud)

让我们定义一个实例并尝试制作一个副本(我已经尝试过这篇文章中提出的解决方案):

  1. 使用replace方法:
d2 = replace(d1, **{})
Run Code Online (Sandbox Code Playgroud)

抛出错误

InitVar 'b' must be specified with replace()
Run Code Online (Sandbox Code Playgroud)

这似乎是一个已报告的错误,但我不确定是否有任何进展。

  1. __dict__通过从旧对象创建新对象:
d2 = D(**d1.__dict__)
Run Code Online (Sandbox Code Playgroud)

抛出错误

__init__() got an unexpected keyword argument 'c'
Run Code Online (Sandbox Code Playgroud)

您对如何正确复制数据类实例或“解决方法”指出的问题有什么建议吗?


编辑:

  • 修复了初始代码中的错误(self.b__post_init__

我已经制定了这个似乎有效的解决方法(发布在答案中)。如果有人能发现缺点,我们将不胜感激。

use*_*ica 13

只需使用标准copy模块:

d2 = copy.copy(d1)
Run Code Online (Sandbox Code Playgroud)

如果你想要深拷贝,你可以使用copy.deepcopy.


任何基于调用数据类构造函数的方法都注定会失败。其中包括replace,它委托给构造函数。问题是 InitVar 值没有存储在任何地方,因此无法知道要为 InitVars 传递哪些值。(特别是,self.b不是提供的值- 它是默认值 - 所以你的值破坏了。)b__post_init__

  • @RomanZhuravlev:什么?数据类没有视图的概念。您无法创建数据类实例的视图,因为不存在这样的东西。它创建了一个*浅*副本,但您的答案的原始版本也是如此,并且您问题中的代码将创建它实际工作的对象的浅副本。如果你想要一个深层复制,你可以使用`copy.deepcopy`,但你从未表明你想要一个。 (3认同)

Rom*_*Zh. -1

我已经做了这个似乎有效的解决方法。如果有人能发现缺点,我们将非常感激。

def copy_dataclass(D_class, d_obj, **kw):
    input = {**kw}
    for key, value in asdict(d_obj).items():
        # If the attribute is passed to __init__
        if d_obj.__dataclass_fields__[key].init:
            input[key] = copy.deepcopy(value)
        
    copy_d = D_class(**input)
    
    return copy_d
Run Code Online (Sandbox Code Playgroud)

这使:

d2 = copy_dataclass(D, d1)

d1 == d2
# prints True

d1 is d2
# prints False
Run Code Online (Sandbox Code Playgroud)

如果我们更改 d2 中的字段,它不会影响 d1

d2.a = 50

print(f'd1 = {d1};\nd2 = {d2}')
# d1 = D(a=10.0, c=30.0);
# d2 = D(a=50, c=30.0)
Run Code Online (Sandbox Code Playgroud)

编辑

  • 添加copy.deepcopy(value)用于数据类属性可变的情况;
  • __dict__替换为asdict()方法;
  • 可以向该方法提供附加输入以替换InitVars属性的默认值
  • 可以添加额外的检查来验证是否所有内容InitVars都已替换(使用D.__dataclass_fields__)。 因此,通过这些修改,我们将得到该replace()方法......