如何在 Python 中对具有不可变类型的副本执行浅更新? (结构分享)

How does one perform a shallow update on copy with an immutable type in Python? (structural sharing)

我要从 Scala 转到 Python,对于给定的(案例)class,我可以执行复制,并在同一步骤中指定修改字段的任意子集.在 Scala 中看起来像这样:

case class Foo(year: Int, album: String)

// here's a Foo
val foo: Foo = Foo(1973, "Larks Tongues In Aspic")

// updated album with (shallow) copy of year in one step
val nooFoo: Foo = foo.copy(album = "Black Beauty: Live at the Fillmore West")

(为便于复制,示例保持简单)

我看到很多关于复制不可变数据但不更新它的堆栈问题。我想在不可变更新的上下文中进行临时结构共享。

我研究过使用 namedtuple 或在 __setattr__ 中引发错误来创建不可变对象。然后我想我可能有调用 object.__setattr__ 的方法,这似乎使本地 __setattr__ 方法过载。 但是,这是否意味着我需要为我可能执行的每个更新组合实现一个方法?这意味着我最多需要定义 n!更新 n 个字段的函数(理论上)。对于具有 2 个字段的 class,可能如下所示:

import copy

class Foo:
  self.year = None
  self.album = None

  def __init__(self, year, album):
    object.__setattr__(self, "year", year)
    object.__setattr__(self, "album", album)

  def __setattr__(self):
    raise TypeError("i pity the Foo")

  def update_a(self, new_year):
    noo_foo = copy.copy(self)
    object.__setattr__(noo_foo, "year", new_year)
    return noo_foo

  def update_b(self, new_album):
    noo_foo = copy.copy(self)
    object.__setattr__(noo_foo, "album", new_album)
    return noo_foo


执行不可变对象更新(具有字段修改的副本)而不陷入编写大量 class 样板文件的 Pythonic 方法是什么?或者,我缺少一些简单的东西吗?

namedtuple supports this behavior with the ._replace method(尽管有前导下划线,但这是一个 public 方法,它只是加了一个下划线前缀,以避免与用户定义的字段冲突),而且它实际上是不可变的(覆盖 __setattr__ 并不是真正不变的)。

from collections import namedtuple

Foo = namedtuple('Foo', 'year album')

foo1 = Foo(1996, 'Album1')
foo2 = foo1._replace(year=1997)

如果您不需要与旧版本的 Python 兼容,并且想要一种简单的方法来向 class 添加额外的行为,the newer typing.NamedTuple 是一种更灵活的方法制作命名元组。