元组 tuple 是不可变序列:创建后不能替换、增加或删除其中的位置,但这些位置引用的对象仍可能是可变的。元组常用于把多个相关值作为一个对象传递;逗号负责打包,圆括号主要负责分组和消除语法歧义。
本文以 Python 3.11 为基线。每段程序可独立运行,示例断言需要在未启用 -O 的情况下执行。
创建、索引、连接与重复
跳转到“创建、索引、连接与重复”空元组写作 ();单元素元组需要尾随逗号,如 (1,) 或 1,。单独的 (1) 仍是整数表达式。
empty = ()single = (1,)pair = 2, 3assert type(empty) is tuple and type(single) is tupleassert (1) == 1 and type((1)) is intassert pair == (2, 3)assert tuple([1, 2]) == (1, 2)
values = (1, 2, 2, 3)assert values[0] == 1 and values[-1] == 3assert values[1:3] == (2, 2) and values[99:] == ()assert values.count(2) == 2 and values.index(2) == 1assert values.index(2, 2) == 2assert len(values) == 4assert 2 in values and "2" not in valuesassert (1, 2, 3) + ("a", "b") == (1, 2, 3, "a", "b")assert 3 * (1, 2) == (1, 2, 1, 2, 1, 2)assert (1, 2) * 0 == ()assert tuple(reversed(values)) == (3, 2, 2, 1)assert (1, 2) < (1, 3) and (1, 2) < (1, 2, 0)
for operation, expected in [ (lambda: values[99], IndexError), (lambda: values.index(99), ValueError), (lambda: (1, "two") < (1, 2), TypeError),]: try: operation() except expected: pass else: raise AssertionError("invalid tuple operation succeeded")print("tuple construction and sequence checks passed")连接和重复计算结果,不会原地更改原元组。不要进一步推断“每次都分配不同身份的元组”:例如 tuple(existing_tuple) 可以直接返回原元组。元组之间的排序仍取决于对应元素是否能比较;它与列表一样使用字典序。
不可变位置与可变内容
跳转到“不可变位置与可变内容”下面的元组不能把第一个位置替换掉,却可以通过该位置拿到列表后修改列表。哈希还要求每个成员都可哈希,因此“外层是元组”不足以保证可以作为字典键。
item = ([1, 2], "sensor")item[0].append(3)assert item == ([1, 2, 3], "sensor")try: item[0] = []except TypeError: passelse: raise AssertionError("tuple slot assignment succeeded")try: hash(item)except TypeError: passelse: raise AssertionError("tuple containing a list became hashable")
key = ("sensor", 3)mapping = {key: "ready"}assert mapping[("sensor", 3)] == "ready"original = (1, 2)alias = originaloriginal += (3,)assert original == (1, 2, 3) and alias == (1, 2)del originalassert alias == (1, 2)
def outputs(values): return (*values,)
packed = outputs([10, 20])assert type(packed) is tuple and packed == (10, 20)first, second = packedassert first == 10 and second == 20head, *tail = (1, 2, 3)assert head == 1 and tail == [2, 3]assert outputs(iter([1, 2])) == (1, 2)print("immutability, hash and packing checks passed")del original 删除的是名称绑定,其他引用仍可访问对象。return (*values,) 先展开可迭代对象,再构建并返回一个元组;不是向调用方返回若干彼此独立的返回对象。调用方的赋值拆包是另一项操作。
原 forward 片段中的星号返回
跳转到“原 forward 片段中的星号返回”原笔记使用 forward(self, raw) 展示 return (*x_out,),并依次调用 m_apply、注意力块、reshape、上采样、求和和归一化激活。原片段没有提供这些网络部件的定义,不能据此还原实际模型。
下面保留相同的数据流名称,补成纯 Python 的标量演示;其中加法和乘法只是用于观察容器传递,不实现真实神经网络层,也不声称验证了原网络。
class TupleFlowDemo: def __init__(self): self.patch_avg = lambda value: value + 1 self.avg_map = lambda value: value * 2 self.attention = [lambda values: [value - 1 for value in values]] self.reshape = lambda value: value self.upconvs = lambda value: value + 10 self.bn_relu = lambda value: max(0, value)
@staticmethod def m_apply(values, operation): return [operation(value) for value in values]
@staticmethod def m_sum(left, right): return [a + b for a, b in zip(left, right, strict=True)]
def forward(self, raw): x = self.m_apply(raw, self.patch_avg) x = self.m_apply(x, self.avg_map) for block in self.attention: x = block(x) x = [self.reshape(value) for value in x] x = self.m_apply(x, self.upconvs) x_out = self.m_sum(x, raw) x_out = self.m_apply(x_out, self.bn_relu) return (*x_out,)
model = TupleFlowDemo()raw = [1, 2]result = model.forward(raw)assert result == (14, 17) and type(result) is tupleassert raw == [1, 2]assert model.forward([]) == ()print("starred return flow checks passed")此例也说明:星号可以用于任何合适的可迭代对象,不要求 x_out 事先就是元组。若输入是只能遍历一次的迭代器,而算法后面还需要再次读取它,应先明确是否保存为容器;该演示的 raw 使用列表。
原表名称与易错例子
跳转到“原表名称与易错例子”| 功能 | 原笔记涉及的名称 | 正确理解 |
|---|---|---|
| 类型、文档与属性 | __class__、__doc__、__dir__、__getattribute__ | 属性与方法不同;日常使用 type、dir、getattr |
| 创建与大小 | __new__、__len__、__sizeof__ | tuple 创建对象;len 数元素;对象内存大小不递归包含所有成员 |
| 读取与遍历 | __getitem__、__contains__、__iter__ | 支持索引、切片、成员判断与迭代 |
| 连接与重复 | __add__、__mul__、__rmul__ | 3 乘二元素元组产生六个元素,原表少写了一组 |
| 表示与格式化 | __repr__、__str__、__format__ | 普通元组不支持直接用 .2f 对每个成员格式化 |
| 比较 | __eq__、__ne__、__ge__、__gt__、__le__、__lt__ | 整数 2 与字符串 "2" 不相等 |
| 哈希与重建 | __hash__、__getnewargs__、__reduce__、__reduce_ex__ | 受成员哈希性及序列化协议约束,常规代码调用 hash 或 pickle |
| 两个公开查询方法 | count()、index() | 计数与查找;index 找不到时抛出 ValueError |
原表的 __reverse__ 不是正确方法名。对普通元组调用 reversed(t) 即可,它可以通过序列协议反向遍历,不要求元组自己提供 __reversed__。序列规则见 Python 元组文档。
import pickleimport sys
values = (1, 2)assert values.__class__ is tuple and isinstance(tuple.__doc__, str)assert "count" in dir(values)assert values.__getattribute__("__class__") is tupleassert values.__getnewargs__() == ((1, 2),)assert not hasattr(values, "__reverse__")assert format(values, "") == str(values)try: format(values, ".2f")except TypeError: passelse: raise AssertionError("tuple accepted per-number format")formatted = tuple(format(value, ".2f") for value in values)assert formatted == ("1.00", "2.00")assert sys.getsizeof(values) >= values.__sizeof__()assert isinstance(hash(values), int)assert pickle.loads(pickle.dumps(values, protocol=2)) == valuesprint("tuple metadata and formatting checks passed")pickle 示例只恢复本程序生成的字节,不用于不可信来源;协议细节见特殊方法与对象协议。如果需要按名称表达不同字段,可进一步考虑命名元组或数据类,而不是让读者长期记住每个位置的含义。