如何优雅地“更改”元组中的一个元素?

3

这很可能是重复的内容,但我的搜索功能无法找到。

众所周知,元组是不可变的,因此您实际上无法更改它们。然而,有时候,做一些类似于将 (1, 2, "three") 更改为 (1, 2, 3) 的事情会很方便,也许与 Haskell 记录更新语法 类似。您实际上不会更改原始元组,但是您将得到一个在仅一个(或多个)元素上有所不同的元组。

一种实现方法是:

elements = list(old_tuple)
elements[-1] = do_things_to(elements[-1])
new_tuple = tuple(elements)

我认为将元组转换为列表有点违背了使用元组类型的初衷:old_tuple。如果您使用列表,每次操作都不必在内存中构建一个丢弃的列表副本。

如果您只想更改元组的第三个元素,也可以这样做:

def update_third_element(a, b, c, *others):
  c = do_things_to(c)
  return tuple(a, b, c, *others)

new_tuple = update_third_element(*old_tuple)

这种方法比朴素的方法更能抵抗元组中元素数量的变化:
a, b, c, d, e, f, g, h, i, j = old_tuple
c = do_things_to(c)
new_tuple = (a, b, c, d, e, f, g, h, j, j) # whoops

...但如果你想要更改的是最后一个或倒数第n个元素,则此方法无效。它还会创建一个临时列表(others)。它还强制你命名前n个元素。

有更好的方法吗?


3
你说“将元组转换为列表有点违背了一开始使用元组类型的初衷”。实际上,需要更改元素才是违背使用元组类型的初衷。如果你发现自己需要经常更改元组的元素,那么它们可能一开始就不应该是元组。如果你不需要经常这样做,那么将其转换为列表可能会有些丑陋但也无妨。 - BrenBarn
@BrenBarn,我不同意不变性(如果您持有对此对象的引用,则它永远不会改变)意味着您不应该希望从中获取对象并从原始对象稍微更改一些而创建新对象。 - badp
对于那些还不信服的人来说,最好的例子当然是 str,它们拥有一个 replace 方法,尽管是不可变的。 - badp
1个回答

3
我建议使用collections.namedtuple代替它:

>>> from collections import namedtuple

>>> class Foo(namedtuple("Foo", ["a", "b", "c"])):
        pass

>>> f = Foo(1, 2, 3)  # or Foo(a=1, b=2, c=3)
>>> f._replace(a = 5)
Foo(a=5, b=2, c=3)

namedtuple还支持索引,因此您可以在普通元组的位置使用它们。

如果必须使用普通元组,只需使用辅助函数:

>>> def updated(tpl, i, val):
        return tpl[:i] + (val,) + tpl[i + 1:]

>>> tpl = (1, 2, 3)
>>> updated(tpl, 1, 5)
(1, 5, 3)

3
+1:_replace 本质上是我正在寻找的元组方法;可惜由于某些原因,tuple 实际上并没有它。 - badp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接