优雅地“更改”元组元素的方法是什么?

3 投票
1 回答
3714 浏览
提问于 2025-04-18 04:31

这很可能是重复的问题,但我搜索的时候没找到相关内容。

大家都知道,元组是不可变的,也就是说你不能真的去改变它们。不过,有时候我们需要做一些类似于把 (1, 2, "three") 变成 (1, 2, 3) 的事情,这种需求就像 Haskell 语言中的记录更新语法一样。实际上,你并不会改变原来的元组,而是会得到一个新的元组,只有一个(或多个)元素不同。

实现这个功能的一种方法是:

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

不过,我觉得把元组转成列表有点违背了使用元组的初衷:如果你用的是列表,就不需要在每次操作时在内存中创建一个临时的列表副本。

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

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 个。

有没有更好的方法呢?

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)

撰写回答