资讯动态

Python基础数据类型tuple元组的概念与用法

发布时间:2026/10/13 3:33:13 来源:尧图企业网站定制
前言tuple元组是 Python 的基础数据类型之一官方文档把它归在「序列类型」下和list并列。它的概念其实一句话能说清一个不可变的有序序列。但就是「不可变」这三个字衍生出了一串连锁反应——不能增删改、可以算哈希、能当字典的键、能放进集合、适合表达固定记录。围绕元组有两个概念经常被说反。第一是「元组就是带圆括号的列表」这是错的圆括号只是可选的装饰真正定义元组的是逗号1, 2是元组(1)是整数。第二是「元组一定可哈希」这也只对了一半元组的哈希要求它里面每个元素都可哈希(1, [2])这样的元组照样不可哈希。本文从概念入手讲清元组「是什么、为什么不可变、不可变带来什么」再覆盖创建、用法、与列表的取舍最后介绍具名元组namedtuple这一实用扩展。一、元组的概念不可变的有序序列官方对序列的通用操作元组几乎全都支持下标、切片、len、in、迭代、、*、比较。它不支持的是「修改自身」这一类。特性元组是否支持按下标取值t[0]支持切片t[1:3]支持len/in/ 迭代支持拼接、重复*支持返回新元组元素赋值t[0] x不支持append/remove/sort不支持# 适用于 Python 3.8t (10, 20, 30)print(t[0]) # 10print(t[0:2]) # (10, 20)print(len(t)) # 3print(20 in t) # Trueprint(t (40,)) # (10, 20, 30, 40)print(t * 2) # (10, 20, 30, 10, 20, 30)print(t) # (10, 20, 30)原元组始终不变看最后一行所有运算都没有改动t这是元组的核心语义。二、逗号才是元组的标志这是入门阶段最容易栽跟头的地方。# 适用于 Python 3.8a 1, 2, 3 # 元组b (1, 2, 3) # 元组c (1) # 整数圆括号只是分组d (1,) # 元组逗号是关键e 1, # 元组括号可以省print(type(a).__name__) # tupleprint(type(c).__name__) # intprint(type(d).__name__) # tupleprint(type(e).__name__) # tupleprint(len(d)) # 1(1)和(1,)只差一个逗号类型却完全不同。空元组()是唯一的例外——它没有元素不存在歧义。这个规则还有一个现实后果函数返回多个值时返回的其实就是元组return 1, 2和return (1, 2)等价。三、不可变带来的可哈希不可变让元组可以参与哈希。规则是元组内每个元素都可哈希时元组整体可哈希。可哈希意味着它能当字典的键、能放进集合。# 适用于 Python 3.8# 元素都可哈希元组可哈希key (2026, 10, 7)d {key: 今天}print(d[(2026, 10, 7)]) # 今天# 元素含列表元组不可哈希bad (1, [2, 3])try:hash(bad)except TypeError as e:print(报错, e) # unhashable type: list第二段说明「元组可哈希」是有条件的。同理(1, {a: 1})也不可哈希因为字典不可哈希。要表达「含可变内容的不可变记录」需要把内层换成不可变对象比如frozenset。# 适用于 Python 3.8safe (1, frozenset({2, 3}))print(hash(safe) is not None) # Trueprint(len({safe, (1, frozenset({2, 3}))})) # 1内容是同一个最后一行把两个内容相同的元组放进集合因为哈希和相等都一致集合里只剩一个元素。四、元组当作记录工程上元组最常见的用法是表达「一条固定记录」。列表强调「同类元素的集合」元组强调「几个不同字段绑在一起」。# 适用于 Python 3.8# 数据库查询结果、坐标、日期区间都适合用元组point (3, 5)rect (0, 0, 100, 200) # 左上角 x, y 和宽高date_range (20260101, 20261231)# 解包让字段名在读取时变得清晰x, y pointleft, top, width, height rectprint(x, y) # 3 5print(left width, top height) # 100 200# 函数返回多个值就是返回元组def divmod_like(a, b):return a // b, a % bquotient, remainder divmod_like(17, 5)print(quotient, remainder) # 3 2元组的比较是逐元素的这让它成为多级排序的天然键。# 适用于 Python 3.8records [(bob, 90), (alice, 90), (carol, 85)]print(sorted(records))# 先比姓名再比分数print(sorted(records, keylambda r: r[1], reverseTrue))sorted(records)直接按元组逐元素比较先比bob和alice所以结果是[(alice, 90), (bob, 90), (carol, 85)]。第二句指定key只比分数并降序。五、具名元组给字段起名字字段一多record[0]、record[1]这种写法就难读了。collections.namedtuple提供了带字段名的元组它仍然是元组仍然不可变、可哈希但可以用属性名访问字段。它的工厂函数签名包含typename、field_names以及只能关键字传递的rename、defaults、module。defaults参数和_field_defaults属性需要Python 3.7 及以上。# 适用于 Python 3.8from collections import namedtuplePoint namedtuple(Point, [x, y])p Point(11, 22)print(p.x, p.y) # 11 22print(p[0]) # 11仍然可以按下标访问print(p) # Point(x11, y22)print(p._fields) # (x, y)# _replace 返回「新」对象原对象不变q p._replace(x33)print(q) # Point(x33, y22)print(p) # Point(x11, y22)p 没变# _asdict 返回普通字典print(p._asdict()) # {x: 11, y: 22}三个下划线开头的成员要记住成员类型作用是否原地修改_make(iterable)类方法从序列构造实例—_asdict()实例方法转成字典否返回新字典_replace(**kwargs)实例方法替换部分字段否返回新实例_fields属性字段名元组—_field_defaults属性字段默认值字典3.7—_replace返回新对象这一点特别容易踩坑很多人写了p._replace(x33)就以为p被改了其实什么都没发生。如果要类型标注typing.NamedTuple提供类语法3.6 及以上# 适用于 Python 3.8from typing import NamedTupleclass Point(NamedTuple):x: inty: int 0 # 类语法里直接写默认值p Point(3)print(p.x, p.y) # 3 0print(p) # Point(x3, y0)print(p._field_defaults) # {y: 0}typing.NamedTuple在 3.6 起支持类语法用注解声明字段默认值直接写在字段上。它生成的对象和collections.namedtuple生成的是一样的元组子类两个都能用_asdict、_replace、_fields。六、实战用元组与具名元组处理订单# 适用于 Python 3.8from collections import namedtupleOrder namedtuple(Order, [order_id, sku, qty], defaults[1])def total_qty(orders, sku):统计某个 SKU 的总数量。默认值参数让它能只传两个字段。return sum(o.qty for o in orders if o.sku sku)def top_sku(orders):找出数量最多的 SKU返回 (sku, 数量)。空的返回 None。if not orders:return Nonetotals {}for o in orders:totals[o.sku] totals.get(o.sku, 0) o.qtysku, qty max(totals.items(), keylambda kv: kv[1])return sku, qtyif __name__ __main__:orders [Order(1, A100, 3),Order(2, B200, 1),Order(3, A100), # 省掉 qty用默认值 1]print(total_qty(orders, A100)) # 4print(top_sku(orders)) # (A100, 4)print(top_sku([])) # None逐行推演Order用defaults[1]给最后一个字段qty一个默认值所以Order(3, A100)也能构造qty自动为 1。total_qty用生成器表达式求和A100的两条记录分别是 3 和 1合计 4。top_sku先用字典累加每个 SKU 的数量再用max(..., key...)取最大值那一项解包成sku, qty返回一个元组。空列表时提前返回None避免max抛异常。常见坑点坑点 1单元素元组漏逗号。❌t (1)之后t[0]抛TypeError: int object is not subscriptable。 ✅ 写t (1,)。坑点 2以为元组可哈希忽略了内部元素。❌d {(1, [2]): x}——内层列表不可哈希整个元组不可哈希抛TypeError。 ✅ 保证元组内所有元素可哈希d {(1, (2,)): x}。坑点 3认为_replace会改原对象。❌p._replace(x1)之后继续用p以为p已更新——_replace返回新实例p不变。 ✅ 接住返回值p p._replace(x1)。坑点 4把*rest当成元组。❌first, *rest (1, 2, 3)后写rest[0]拿到2没问题但以为rest是元组——它是列表[2, 3]。 ✅ 需要元组就rest tuple(rest)通常直接用列表即可。坑点 5用修改元组后以为别名也变了。❌a (1, 2); b a; a (3,)后以为b (1, 2, 3)——b仍是(1, 2)。 ✅ 记住元组的是新建加重新绑定要共享修改语义请改用列表。坑点 6以为元组和列表可以相等。❌(1, 2) [1, 2]期待True——类型不同结果是False。 ✅ 先转换再比tuple([1, 2]) (1, 2)得到True。坑点 7collections.namedtuple默认值用错版本。❌ 在 Python 3.6 上写namedtuple(P, [x, y], defaults[0])——defaults参数是3.7 才加入的3.6 上抛TypeError。 ✅ 3.7 及以上用defaults注意它只作用于最右边的字段更早版本要么升级要么在构造函数里自己处理缺省。坑点 8以为具名元组可以改字段。❌p Point(1, 2); p.x 9——具名元组仍是元组字段赋值抛AttributeError。 ✅ 用p p._replace(x9)得到新实例。总结概念说明本质不可变的有序序列语法标志逗号不是圆括号(1)是整数(1,)是元组可哈希条件内部每个元素都可哈希能做而列表不能当字典键、放进集合专有方法count(value, /)、index(value[, start[, stop]])具名元组collections.namedtuple工厂与typing.NamedTuple类语法3.6_replace返回新实例不改原对象理解元组的关键是顺着「不可变 → 可哈希 → 能当键和集合元素 → 适合表达固定记录」这条链条走一遍。当一段数据从创建到销毁都不该变化时用元组既表达了意图也拿到了可哈希这个额外能力。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑