资讯详情

资讯详情

Python中元组的基础介绍及常用操作总结

前言元组tuple是 Python 的内置序列类型之一和列表长得很像区别只有一个元组不可变。创建之后不能增、不能删、不能改任何看起来在「修改」元组的操作实际都是新建了一个元组。正因为不可变元组可以计算哈希值前提是里面每个元素都可哈希于是能当字典的键、能放进集合这是列表做不到的。元组的语法有一个著名的陷阱逗号才是元组的标志圆括号不是。t 1, 2, 3是元组t (1)是整数t (1,)才是只含一个元素的元组。很多人被这一点绊过。本文先讲元组的基本概念再系统盘点它的常用操作索引切片、count/index、拼接与重复、解包、遍历、排序与转换并配一份完整的可运行示例和坑点清单。一、创建元组逗号是关键官方文档对元组的定义是「用逗号分隔的多个值组成的序列」圆括号是可选的、主要用于分组。所以下面几种写法都成立。# 适用于 Python 3.8t1 1, 2, 3 # 不加括号t2 (1, 2, 3) # 加括号t3 () # 空元组t4 (1,) # 单元素元组逗号不能省t5 tuple([1, 2, 3]) # 从可迭代对象转换t6 tuple(abc) # (a, b, c)print(t4, len(t4)) # (1,) 1print(type((1)).__name__) # int这个是整数print(type((1,)).__name__) # tuple这个才是元组print(t6) # (a, b, c)最后两行的对比值得记牢(1)只是一对起分组作用的括号里面的1依然是整数(1,)带上逗号才构成元组。空元组是例外()没有元素所以不需要逗号区分。tuple(iterable)的构造器可以吃任何可迭代对象字符串会被逐字符拆开字典会被拆成键。# 适用于 Python 3.8print(tuple({a: 1, b: 2})) # (a, b)只取键print(tuple(range(3))) # (0, 1, 2)二、索引、切片与成员判断元组是序列支持所有通用序列操作按下标取、切片、len、in、min、max、sum、迭代。# 适用于 Python 3.8t (10, 20, 30, 40, 50)print(t[0], t[-1]) # 10 50print(t[1:4]) # (20, 30, 40)切片返回新元组print(t[::-1]) # (50, 40, 30, 20, 10)print(len(t)) # 5print(30 in t) # Trueprint(max(t), min(t)) # 50 10print(sum(t)) # 150for value in t:pass # 可以正常迭代有一个和列表共通的坑切片不会越界报错但按下标访问会。t[1:100]返回从 1 到末尾的元组而t[100]直接抛IndexError。元组没有.append、.remove、.pop、.sort、.reverse这些方法因为它们都会修改内容。写t.append(1)会得到AttributeError: tuple object has no attribute append。三、count 与 index元组只有两个专属方法官方签名是sequence.count(value, /)返回value出现的次数。sequence.index(value[, start[, stop]])返回value第一次出现的下标可以限定搜索的起止区间找不到抛ValueError。# 适用于 Python 3.8t (1, 2, 2, 3, 2, 4)print(t.count(2)) # 3print(t.count(99)) # 0不存在返回 0不报错print(t.index(2)) # 1第一次出现的下标# index 可以限定区间print(t.index(2, 2)) # 2从下标 2 开始找try:t.index(99)except ValueError as e:print(报错, e) # tuple.index(x): x not in tuplecount找不到返回0而index找不到抛异常——这两个方法对「不存在」的处理方式相反是很容易记混的一点。要安全取值可以先用in判断或者捕获ValueError。四、拼接、重复与赋值拼接两个元组*重复元组内容两者都返回新元组原元组不变。# 适用于 Python 3.8a (1, 2)b (3, 4)print(a b) # (1, 2, 3, 4)print(a * 2) # (1, 2, 1, 2)print(a) # (1, 2)a 没有变print(b) # (3, 4)b 没有变c ac (9,) # 对元组是「新建并重新绑定」print(c) # (1, 2, 9)print(a) # (1, 2)a 不受影响最后四行是一个重要区别。对列表做是原地扩展所有指向该列表的变量都会看到变化对元组做只能新建一个元组再让变量重新指向它原来的元组纹丝不动。这个差异在多个变量共享同一对象时非常关键。# 适用于 Python 3.8lst [1, 2]alias_list lstlst [3]print(alias_list) # [1, 2, 3]列表原地改别名看到变化tpl (1, 2)alias_tuple tpltpl (3,)print(alias_tuple) # (1, 2)元组新建别名看不到变化五、解包元组最好用的地方元组支持解包unpacking把元素一次性分给多个变量。这是元组在日常代码里出现频率最高的用途。# 适用于 Python 3.8point (3, 5)x, y pointprint(x, y) # 3 5# 交换变量不需要临时变量a, b 1, 2a, b b, aprint(a, b) # 2 1# 用 * 收集剩余元素3.0 起支持星号表达式first, *middle, last (1, 2, 3, 4, 5)print(first) # 1print(middle) # [2, 3, 4]注意这里是列表print(last) # 5*middle收集到的是一个列表不是元组这一点容易看错。解包时左右两边的元素个数必须匹配否则抛ValueError: too many values to unpack或not enough values to unpack。函数返回多个值本质就是返回一个元组然后在调用处解包。# 适用于 Python 3.8def min_max(values):return min(values), max(values) # 返回的是元组low, high min_max([3, 1, 4, 1, 5])print(low, high) # 1 5六、排序与转换元组自己没有排序方法需要排序时用内置函数sorted()它返回排序后的列表。# 适用于 Python 3.8t (3, 1, 4, 1, 5)print(sorted(t)) # [1, 1, 3, 4, 5]返回列表print(list(t)) # [3, 1, 4, 1, 5]转列表print(tuple(sorted(t))) # (1, 1, 3, 4, 5)再转回元组print(t) # (3, 1, 4, 1, 5)原元组不变元组之间可以比较大小规则和列表一致逐元素比较遇到第一个不相等的元素就决定结果。# 适用于 Python 3.8print((1, 2) (1, 3)) # True第二个元素 2 3print((1, 2) (1, 2, 0)) # True前缀相同则短的更小print((2,) (1, 9)) # True第一个元素 2 1这个「逐元素比较」的特性让元组常被用作多级排序的键比如sorted(items, keylambda item: (item.priority, item.name))。七、实战用元组保存不可变记录# 适用于 Python 3.8def split_name(full_name):拆分姓名返回不可变的 (姓, 名)。parts full_name.strip().split()if len(parts) 2:return (parts[0], ) if parts else (, )return (parts[0], .join(parts[1:]))def sort_records(records):records 是 (姓名, 分数) 的元组列表按分数降序、姓名升序排。return sorted(records, keylambda r: (-r[1], r[0]))if __name__ __main__:print(split_name(Ada Lovelace)) # (Ada, Lovelace)print(split_name(Cher)) # (Cher, )data [(alice, 90), (bob, 90), (carol, 85)]for name, score in sort_records(data):print(name, score)逐行推演split_name里parts是列表返回时用圆括号包成元组靠逗号构成元组。对于只有一个词的输入返回(名字, )空输入返回(, )。sort_records的排序键是(-r[1], r[0])先按分数取负升序等价于分数降序分数相同时按姓名升序。输出顺序是alice 90、bob 90、carol 85——alice和bob分数相同按姓名升序排在前面的是alice。最后for name, score in ...对每个元组解包。常见坑点坑点 1单元素元组漏了逗号。❌t (1)以为它是元组——实际是整数1len(t)会抛TypeError。 ✅ 写t (1,)逗号不能省不加括号的t 1,也可以。坑点 2试图修改元组。❌t (1, 2); t[0] 9——抛TypeError: tuple object does not support item assignment。 ✅ 需要修改就新建t (9,) t[1:]频繁修改的场景改用列表。坑点 3以为append存在于元组。❌t (1, 2); t.append(3)——抛AttributeError: tuple object has no attribute append。 ✅ 拼接出新元组t t (3,)。坑点 4把对元组的行为当成原地修改。❌ 认为t (1,)会改掉所有指向t的引用——元组不可变是新建并重新绑定别的引用看不到变化。 ✅ 需要共享修改语义时改用列表用元组就要接受「重新绑定」。坑点 5解包个数不匹配。❌a, b (1, 2, 3)——抛ValueError: too many values to unpack (expected 2)。 ✅ 用星号接住多余项a, b, *rest (1, 2, 3)rest得到[3]。坑点 6以为*rest收集到的是元组。❌first, *rest (1, 2, 3)后认为rest是元组——它实际是一个列表[2, 3]。 ✅ 需要元组就显式转换rest tuple(rest)。坑点 7混淆count和index找不到时的行为。❌ 用t.count(x)判断是否存在后又以为t.index(x)在不存在时返回-1——它会抛ValueError。 ✅ 判断存在用x in t用index时捕获ValueError。坑点 8以为元组一定可哈希。❌t (1, [2, 3]); {t: x}——内层列表不可哈希整个元组随之不可哈希抛TypeError。 ✅ 保证元组内所有元素可哈希比如把内层换成(2, 3)或frozenset({2, 3})。总结操作写法是否新建对象创建(1, 2)/1, 2/(1,)/tuple(iterable)是取值t[i]、t[a:b]切片是新元组计数t.count(v)否返回整数查下标t.index(v[, start[, stop]])否找不到抛ValueError拼接t other是重复t * n是解包a, b t、a, *rest trest是列表排序sorted(t)返回列表是否存在v in t否元组的全部价值来自「不可变」这三个字不可变带来可哈希可哈希带来「能当字典键、能进集合」不可变也带来「共享安全」多处引用的元组不会被谁偷偷改掉。把元组当结构化的常量来用而不是当「不能改的列表」来用才能真正用对。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →