【问题标题】:Replace attributes in Data Class objects替换数据类对象中的属性
【发布时间】:2018-09-29 05:00:43
【问题描述】:

我想替换dataclass 实例的属性,类似于namedtuple._replace(),即制作原始对象的更改副本:

from dataclasses import dataclass
from collections import namedtuple

U = namedtuple("U", "x")

@dataclass
class V:
    x: int

u = U(x=1)
u_ = u._replace(x=-1)
v = V(x=1)

print(u)
print(u_)
print(v)

这会返回:

U(x=1)
U(x=-1)
V(x=1)

如何在数据类对象中模拟此功能?

【问题讨论】:

    标签: python python-3.7 python-dataclasses


    【解决方案1】:
    @dataclass()
    class Point:
        x: float = dataclasses.Field(repr=True, default=0.00, default_factory=float, init=True, hash=True, compare=True,
                                     metadata={'x_axis': "X Axis", 'ext_name': "Point X Axis"})
        y: float = dataclasses.Field(repr=True, default=0.00, default_factory=float, init=True, hash=True, compare=True,
                                     metadata={'y_axis': "Y Axis", 'ext_name': "Point Y Axis"})
    
    Point1 = Point(13.5, 455.25)
    Point2 = dataclasses.replace(Point1, y=255.25)
    
    print(Point1, Point2)
    

    【讨论】:

    • 欢迎来到 StackOverflow!您能否在您的答案中添加一些文字来解释它是如何解决问题的,也许还可以指出我们如何将它添加到已经提供的其他答案中?
    【解决方案2】:

    我知道问题是关于dataclass,但如果您使用的是attr.s,那么您可以使用attr.evolve 而不是dataclasses.replace

    import attr
    
    @attr.s(frozen=True)
    class Foo:
        x = attr.ib()
        y = attr.ib()
    
    foo = Foo(1, 2)
    bar = attr.evolve(foo, y=3)
    

    【讨论】:

      【解决方案3】:

      Python 数据类模块具有用于在数据类实例上进行字段替换的公共 API,记录在 here。此功能由模块级辅助函数提供:

      from dataclasses import replace
      

      用法与 collections.namedtuple 不同,后者通过生成类型上的方法提供此功能(旁注: namedtuple._replace 已记录/公开。此名称选择被称为“遗憾” “作者 - 请参阅答案末尾的链接)。

      示例用法:

      >>> from dataclasses import dataclass, replace
      >>> @dataclass
      ... class V:
      ...     x: int
      ...     y: int
      ...     
      >>> v = V(1, 2)
      >>> v_ = replace(v, y=42)
      >>> v
      V(x=1, y=2)
      >>> v_
      V(x=1, y=42)
      

      有关设计的更多背景信息,请参阅 PyCon 2018 演讲 - Dataclasses: The code generator to end all code generators。深入讨论了replace API,以及namedtupledataclasses 之间的其他设计差异以及一些性能比较。

      【讨论】:

      • 不幸的是,Raymond 最近的谈话充满了糟糕的建议。还有 this one 支持各种实践(很大程度上依赖于上下文管理器进行设置/拆卸,很大程度上依赖于魔术方法的自定义覆盖),这些实践在生产 Python 中显然是不好的反模式。看完链接的谈话后,我感觉不太相信replace 是个好主意。
      • 似乎有人发现了 init 和数据类中的 post-init 钩子的问题,他们没有重新审视设计并解决复杂性,而是选择通过增加复杂性来解决它。真正的故事是,如果您以某种方式利用数据类,而它们不被视为完全无逻辑的容器,那么您使用它们是错误的,您需要一个不同的工具。例如,数据类的deepcopy 除了对每个成员属性进行简单的深度复制之外,做任何事情的风险都应该绝对为零,因此对于用户来说不会有任何意外的问题。
      • 换句话说,如果您使用 dataclass 的方式后来 replace 实际上会产生重大影响,那么这是一个主要的代码异味/危险信号,它会呈现 replace非常没有实际意义,并且可以很好地直接调整属性。不幸的是,这个责任被赋予了编码人员而不是实现,但这并没有改变它。
      • replace 在具有(伪)不可变对象(例如冻结的数据类)时非常有用。它们在函数式编程中非常常见,您不会改变原始对象,而是返回一个所有字段都相等的新对象,除了您 replace 的字段。
      【解决方案4】:

      dataclass 只是用于自动创建特殊的 __init__ 方法和许多其他基于类型注释属性的“样板”方法的语法糖。

      一旦创建类,它就和其他类一样,它的属性可以被覆盖,实例可以被复制,例如

      import copy
      
      v_ = copy.deepcopy(v)
      v_.x = -1
      

      根据属性的不同,您可能只需要copy.copy

      【讨论】:

      • –1 在数据类上使用复制/深复制进行字段替换是不正确的。在一些复杂的用例中(例如 init/post_init 钩子),可能无法正确处理数据。更好的方法是使用dataclasses.replace()函数。
      • @wim 看来您正在查看我的回答历史以惩罚性地否决/争论,可能是对my opinion 对您提出的问题和答案的否决的报复。请停止这种行为,因为我的经验让我觉得它在这一点上近乎滥用。
      • 不,这是巧合。我是 [python-dataclasses] 标签的追随者(目前是唯一的追随者)。 replace 方法的存在在我昨天参加 PyCon 讨论时引起了我的注意。
      • @wim 稍后再谈,我认为在生产系统中处理此功能一段时间后,我对replace 的分歧更加强烈。我在您的答案中添加了一些 cmets 以获得不同的效果。我完全尊重你的 POV 是不同的,但我想强调一个不同的意见,因为有些用户可能觉得我这样做,它可以告知他们使用基于约定的限制的方法 dataclass 允许避免不良代码气味replace.
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-07
      • 1970-01-01
      相关资源
      最近更新 更多