Python元组)
前言这是入门系列的第 13 篇前面我们已经学过数字、字符串、列表这些基础类型本篇讲第四种常用的序列类型——元组tuple。它是本系列里第一个「不可变」的容器一旦创建内容就定下来了。初学者常常有两个误解。第一个是「元组就是加了圆括号的列表」其实不是——(1, 2)和[1, 2]的区别在于元组改不了列表能改而能不能改决定了它们适合的用途完全不同。第二个是「只要加了括号就是元组」这也不对元组的标志是逗号而不是括号(1)是整数(1,)才是元组。本篇按「怎么创建、怎么读取、怎么使用、什么时候用」的顺序一步步来每个知识点都配一段可以直接复制运行的代码。一、元组长什么样元组是把若干个值用逗号连起来写成的序列。圆括号可以加也可以不加加了主要是为了看起来清楚也为了在表达式里分组。# 适用于 Python 3.8# 这几种写法得到的是同一个东西t1 1, 2, 3t2 (1, 2, 3)print(t1 t2) # Trueprint(type(t1).__name__) # tupleprint(t1) # (1, 2, 3)print出来时 Python 会自动补上圆括号所以看到(1, 2, 3)就知道它是元组。空元组写()只有一个元素的元组必须写成(1,)逗号是必须的。# 适用于 Python 3.8empty ()single (1,)not_tuple (1) # 这是一个整数不是元组print(len(empty)) # 0print(len(single)) # 1print(type(not_tuple).__name__) # int初学者看到(1,)会觉得那个逗号多余但它是用来区分「元组」和「普通括号」的唯一标志省不得。同理type((1))是inttype((1,))才是tuple。二、读取元组里的元素元组的读取方式和列表一模一样因为它俩都是序列。可以用下标取值可以用切片取一段可以用len数长度可以用in判断某个值在不在里面。# 适用于 Python 3.8weekdays (周一, 周二, 周三, 周四, 周五)print(weekdays[0]) # 周一print(weekdays[-1]) # 周五负数从末尾数print(weekdays[1:3]) # (周二, 周三)切片左闭右开print(len(weekdays)) # 5print(周一 in weekdays) # True下标从 0 开始这一点和列表相同。切片[1:3]取的是下标 1 和 2不含 3也就是常说的「左闭右开」。切片的结果仍然是元组。如果下标超出范围会抛异常。# 适用于 Python 3.8weekdays (周一, 周二)try:print(weekdays[5])except IndexError as e:print(报错, e) # tuple index out of range但要记住切片不会越界报错。weekdays[0:100]只会返回能取到的部分不抛异常。这是列表和元组共通的特性别因为一个越界了、一个没越界就觉得奇怪。三、元组改不了这是元组最核心的特点。想给某个位置重新赋值会直接报错。# 适用于 Python 3.8point (3, 5)try:point[0] 9except TypeError as e:print(报错, e) # tuple object does not support item assignment元组也没有append、remove、pop、sort、reverse这些方法因为它们都要修改内容。dir()可以看到元组只有count和index两个专属方法。# 适用于 Python 3.8t (1, 2, 2, 3)print(t.count(2)) # 22 出现了两次print(t.index(3)) # 33 第一次出现在下标 3虽然不能改但可以用拼接的办法造一个新元组原来的元组始终不变。# 适用于 Python 3.8t (1, 2)t2 t (3,)print(t) # (1, 2)原来的没变print(t2) # (1, 2, 3)这是新元组四、元组的解包解包是元组用起来最舒服的地方。把一个元组的元素一次性分给几个变量# 适用于 Python 3.8person (小明, 12, 六年级)name, age, grade personprint(name) # 小明print(age) # 12print(grade) # 六年级左右两边的个数必须对上多了少了都会报错。# 适用于 Python 3.8try:a, b (1, 2, 3)except ValueError as e:print(报错, e) # too many values to unpack (expected 2)如果只关心前面几个可以用星号把剩下的都收走# 适用于 Python 3.8first, *rest (1, 2, 3, 4)print(first) # 1print(rest) # [2, 3, 4]注意这里收到的是列表星号收集到的是列表而不是元组这是入门阶段很容易看漏的一个细节。有了元组和拼接交换两个变量就不需要中间变量了# 适用于 Python 3.8a, b 1, 2a, b b, aprint(a, b) # 2 1右边b, a先打包成一个元组(2, 1)再解包给a, b所以能一步完成交换。五、元组和列表怎么选初学者最容易问的问题就是「什么时候用元组什么时候用列表」。下表把两者的差别列清楚。对比项元组 tuple列表 list写法(1, 2)或1, 2[1, 2]能否修改不能能能否增删元素不能能能否当字典键元素可哈希时可以不可以能否放进集合元素可哈希时可以不可以专属方法count、indexappend、extend、sort等一大批典型用途固定的记录、常量组合会变化的同类型数据集合选择的经验法则是如果这一组数据从生到死都不该变用元组如果它会增删改用列表。比如坐标(3, 5)、日期(2026, 10, 7)、数据库查询返回的一行记录都适合元组而购物车里的商品、待办事项列表适合列表。六、完整示例下面这段小程序把本篇讲的知识串起来保存几条书籍记录做查询和统计。# 适用于 Python 3.8BOOKS ((Python编程, 张三, 2020),(数据结构, 李四, 2019),(算法导论, 王五, 2021),)def find_by_author(author):返回该作者的所有书结果是列表。return [title for title, who, year in BOOKS if who author]def years_of(titles):titles 是书名集合返回对应年份列表。result []for book in BOOKS:title, _, year bookif title in titles:result.append(year)return resultif __name__ __main__:print(find_by_author(张三)) # [Python编程]latest max(BOOKS, keylambda b: b[2])print(最新的一本, latest[0]) # 最新的一本算法导论print(年份, years_of({Python编程, 算法导论})) # 年份 [2020, 2021]逐行推演BOOKS是一个元组里面装着三个小元组每个小元组是一条记录。find_by_author用列表推导式把BOOKS里每个小元组解包成title, who, year筛出作者匹配的书名。max(BOOKS, keylambda b: b[2])用第三个元素年份作为比较键找出年份最大的那条记录也就是(算法导论, 王五, 2021)取[0]得到书名。years_of里用title, _, year book解包中间那项用下划线_接住、表示「用不到」这是一种约定俗成的写法下划线在这里只是个普通变量名。titles用的是集合in判断很快。常见坑点坑点 1单元素元组漏逗号。❌t (5)后写t.count(5)——t是整数抛AttributeError: int object has no attribute count。 ✅ 写t (5,)逗号是元组的标志。坑点 2想修改元组元素。❌t (1, 2); t[0] 9——抛TypeError元组不支持元素赋值。 ✅ 需要可变就一开始就用列表t [1, 2]或者用t (9,) t[1:]造新元组。坑点 3以为元组有append。❌t (1,); t.append(2)——抛AttributeError。 ✅ 用拼接t t (2,)。坑点 4解包个数不匹配。❌a, b (1, 2, 3)——抛ValueError: too many values to unpack。 ✅ 数清楚两边个数或者用a, b, *rest (1, 2, 3)。坑点 5以为星号收集到的是元组。❌head, *tail (1, 2, 3)后把tail当元组用——它是列表[2, 3]。 ✅ 需要元组就转一下tail tuple(tail)。坑点 6拿列表当字典键。❌d {[1, 2]: x}——列表不可哈希抛TypeError: unhashable type: list。 ✅ 换成元组d {(1, 2): x}元素都可哈希时元组就可用作键。坑点 7以为(1, 2)和[1, 2]相等。❌print((1, 2) [1, 2])期待True——不同类型比较相等返回False。 ✅ 先统一类型再比较print(tuple([1, 2]) (1, 2))得到True。坑点 8切片越界和下标越界分不清。❌ 以为t[0:100]会像t[100]一样抛IndexError——切片不越界返回能取到的部分。 ✅ 下标越界才抛IndexError要防越界可以先判断0 i len(t)。总结知识点要点创建(1, 2)、1, 2、tuple(可迭代对象)单元素(1,)逗号不能少(1)是整数空元组()读取下标、切片、len、in和列表一样修改不允许任何操作都返回新元组专属方法只有count和index解包a, b ta, *rest t中rest是列表用途存放不该变的记录可当字典键和集合元素学完本篇你应该能分清元组和列表最本质的区别一个不可变、一个可变。写代码时先问自己「这组数据以后会不会变」答案自然就出来了。