Python列表与元组完全解析:从原理到实战避坑指南
2026/9/24 22:41:10 网站建设 项目流程

开始之前先说一个我观察到的现象:在Python社群混久了你会发现,列表和元组这对"孪生兄弟"是新手翻车率最高的两个数据类型。明明写起来都是方括号小括号一包,顶多一个能改一个不能改,怎么一上项目就处处踩坑?这篇文章我就把它们从头到尾拆一遍,包括创建、索引、切片、增删改查、排序、去重,再到怎么选型、怎么躲开那些隐蔽的坑,最后用一个入门练习题把知识点串起来。不管你是刚打开Python教程的新手,还是已经写过一阵子但没系统梳理过这两类序列的人,都可以对照着看看还有没有盲区。

1. 先搞清楚:列表和元组在Python里到底是什么

1.1 从"有序序列"这个底层概念说起

很多人学列表时第一个误区就是把它当成"数组",然后拿C语言那套思路往上面套。实际上Python里的list比传统数组要灵活得多。它底层是一个动态数组,但为了支持异构元素,每个槽位存的是对象的引用,所以同一个列表里可以同时放整数、字符串、甚至是另一个列表。

真正需要先建立的认知是:列表和元组都属于序列类型(sequence)。所谓序列,就是元素之间有先后顺序、可以通过位置编号来访问的一类数据结构。Python里的字符串、range对象也都是序列。序列的几个通用特性——有序性、索引访问、切片、拼接、重复、成员判断——列表和元组全都继承了下来,这也是为什么你经常看到两兄弟在某些用法上出奇地一致。

很多新手会纠结"列表是有序序列吗"这个问题。答案是肯定的,而且是严格有序。这里的有序不是指元素按大小排序,而是指插入顺序会被记住、永远有一个确定的编号位置。比如你往列表里塞了一个"苹果"、一个"香蕉",这个先后关系就会一直保留下去,除非你主动修改它。这一点和集合(set)有本质区别,集合为了追求哈希查找效率,故意不去记录顺序。

1.2 可变与不可变:列表和元组最本质的差异

如果只让我用一句话概括两者的区别,那就是:列表是可变的(mutable),元组是不可变的(immutable)

可变意味着你可以对列表做增、删、改操作,列表的长度和内容都可以动态变化。你往列表里append一个新元素,内存地址没有变,但对象内容变了。元组则恰恰相反,一旦创建完毕,你就不能再往里面添加元素、删除元素,或者把某个位置的值改成别的值。任何这类操作都会直接抛出异常。

我见过不少同学用一元硬币来理解可变与不可变:列表就像现金钱包,你随时可以往里面塞钱、抽钱出来;元组则像一张已签字的合同,条款定死之后就不能再改了。这个类比其实还算贴切,但有一点要注意:合同的"不能改"只是说纸张上的文字不能变,如果合同里夹着一张活页纸,这张纸上的内容还是可以换的。对应到元组,就是元组里的元素如果是可变对象(比如列表),那这个列表内部的元素依然可以增删改。这是个非常隐蔽的细节,后面我会专门展开。

1.3 为什么Python同时需要两种"长得像"的类型

既然列表这么全能,为什么Python不干脆只要一个列表?这个问题背后其实藏着设计哲学。

不可变类型有几个天然优势。第一,安全。你把一个元组传给函数或别人使用,不用担心对方不小心改了你的数据;如果传的是列表,被改了你可能还完全不知情,排查起来非常痛苦。第二,可哈希。Python里只有不可变类型才能被哈希,所以元组能当作字典的键、放进集合里,而列表不行。第三,确定性。在并发、多线程场景下,不可变对象天然线程安全,不会出现数据竞争问题。第四,性能。元组结构更简单,创建和访问的微观性能通常优于列表,内存占用也更小。

所以这俩不是"一个能打一个不能打"的关系,而是各司其职。列表负责动态变化的数据集,元组负责固定结构、不容篡改的数据记录。理解了这一层,后面选型时就不用纠结了。

2. 列表的创建、索引与切片

2.1 创建列表的四种方式,你常用哪一种

第一种也是最直观的,用方括号直接写出元素:

lst = [1, 2, 3]

第二种,用list()构造函数把一个可迭代对象转成列表。字符串、元组、range、字典的键、集合都可以传进去:

lst1 = list("hello") # ['h', 'e', 'l', 'l', 'o'] lst2 = list(range(5)) # [0, 1, 2, 3, 4] lst3 = list((1, 2, 3)) # [1, 2, 3]

第三种是列表推导式,这个后面专门讲,先留个印象:

squares = [x**2 for x in range(10)]

第四种是创建空列表再往里面添加,比如先lst = [],再用append()慢慢填充。这种方式在循环读数据时最常用。

创建列表时有个常见坑:想做一个二维列表,很多人会写[[0] * 3] * 3,结果发现改一个元素,整列都跟着变。原因就是*操作符做的是浅复制,三个子列表其实指向了同一个对象。正确写法是[[0] * 3 for _ in range(3)],每个子列表独立创建。

2.2 索引:正着数、倒着数,都是0开头

列表是有序序列,访问单个元素用方括号加索引。索引从0开始,这是Python和很多编程语言的通行规则。从前往后数,第一个元素是lst[0],第二个是lst[1]。Python还提供了一个极其方便的负数索引机制:从后往前数,最后一个元素是lst[-1],倒数第二个是lst[-2]

lst = [10, 20, 30, 40] print(lst[0]) # 10 print(lst[-1]) # 40

负数索引的本质是len(lst) + 负索引,例如lst[-1]就等于lst[3]。它在处理"取最后一个元素"这种高频场景时非常顺手,不用先算长度再用正索引。

索引越界会直接抛IndexError: list index out of range,这是入门阶段遇到最多的报错之一。判断一个索引是否合法,核心就是看它是否落在[-len(lst), len(lst)-1]这个区间里。如果你不确定列表是否为空,访问前先做条件判断,或者用if lst:来判断非空列表。

2.3 切片:一次取一段,还能跳着取

切片是列表操作里的重头戏,语法是lst[start:stop:step]。它的理解诀窍只有一个:含头不含尾,也就是取了start位置,但不取stop位置。它返回的是一个新列表,原列表不会受影响。

lst = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(lst[2:5]) # [2, 3, 4] print(lst[:4]) # [0, 1, 2, 3],start省略默认从0开始 print(lst[4:]) # [4, 5, 6, 7, 8, 9],stop省略默认到末尾 print(lst[::2]) # [0, 2, 4, 6, 8],步长为2,跳着取 print(lst[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],步长为负数实现反转

切片还支持负步长,这是很多入门教材一笔带过的玩法。lst[::-1]能一行代码反转列表,看起来非常优雅。再比如lst[-3:]取最后三个元素,lst[:-3]取除最后三个以外的所有元素,这些写法在数据处理中很常见。

需要特别提醒的是:切片返回新列表这个特性可以用来做浅拷贝new_lst = lst[:]。很多新人不知道这个写法,想要复制列表时直接写new_lst = lst,结果修改新列表把原列表也改了。这个问题我在6.3节会仔细讲,这里先记住:直接赋值复制的是引用,切片复制的是内容。

2.4 列表拼接与重复的隐藏细节

两个列表直接用+号可以拼成一个新列表:

a = [1, 2] b = [3, 4] c = a + b # [1, 2, 3, 4]

*号则让列表重复若干次:

d = [0] * 5 # [0, 0, 0, 0, 0]

这两个操作看起来平平无奇,但隐藏着一个性能问题:+拼接会创建一个全新的列表,并把两边元素逐个复制进去。如果你在循环里用lst = lst + [x]这种写法,时间复杂度是O(n²),列表越长越慢。有经验的开发者会在循环里用append,或者先收集到一个列表再一次性扩展,比如用extend方法。后者我会在下一节讲方法时细说。

3. 列表的增删改查与排序

3.1 增删改查四大类方法,用对场景效率翻倍

列表方法按功能分成几组,我按日常使用频率给你捋一遍。

增加元素

  • append(x):在末尾追加一个元素,原地修改列表,返回None
  • extend(iterable):把一个可迭代对象中的每个元素逐个追加到末尾。
  • insert(i, x):在指定位置插入一个元素,原位置及之后的元素都往后挪。

appendextend的区别是新手最容易弄混的点。append([1, 2])是把[1, 2]作为一个整体元素放进去,结果变成[..., [1, 2]]extend([1, 2])是把两个元素拆开追加,结果变成[..., 1, 2]。记住这一点,调试的时候能省不少事。

删除元素

  • remove(x):从左往右找到第一个等于x的元素并删除;如果找不到,抛ValueError
  • pop(i):删除并返回指定位置的元素;不传参数时默认删除并返回最后一个元素。它是唯一一个既能删又能取回值的方法。
  • clear():清空所有元素。
  • del语句:del lst[i]删除指定位置元素,del lst[a:b]删除一段切片。

这里插一个很好用的技巧:当你需要"边遍历边删除"时,直接for x in lst: lst.remove(x)会出大问题。因为遍历过程中列表长度变了,元素会漏掉。常见的替代方案是倒序遍历删除,或者先拷贝一份副本再遍历,例如for x in lst[:]:

修改元素:直接对索引赋值即可,例如lst[0] = 100。需要注意,lst[5] = x并不会在索引5没有元素时自动扩展列表,那样会抛IndexError

查找元素

  • index(x):返回第一个等于x的索引;不存在则抛ValueError
  • count(x):统计x在列表中出现的次数。
  • in操作符:判断元素是否存在,返回布尔值。这个不常用到方法,但查询频率极高。

查找类操作都是线性扫描,时间复杂度O(n),元素多的时候要考虑性能。如果查询极其频繁,建议换成集合或者字典来加速。

3.2 sort和sorted:排序的两种姿势

list.sort()是列表的方法,原地排序,直接把原列表排好,返回Nonesorted()是内置函数,返回一个新列表,原列表不受影响。选哪个取决于你是否需要保留原顺序。

lst = [3, 1, 2] lst.sort() print(lst) # [1, 2, 3] lst2 = [3, 1, 2] sorted_lst = sorted(lst2) print(lst2, sorted_lst) # [3, 1, 2] [1, 2, 3]

这两个函数都支持两个关键参数:keyreversekey接收一个函数,用来指定排序依据。比如有一个列表,每个元素是(名字, 分数)元组,你想按分数从高到低排:

students = [("张三", 88), ("李四", 92), ("王五", 75)] students.sort(key=lambda x: x[1], reverse=True)

这里的lambda x: x[1]就是lambda表达式在列表操作中最经典的使用场景之一。它定义了一个匿名函数,传入每个元素,取出该元素的第二个字段作为排序键。其实Python排序是稳定的,如果分数相同,会保留原始相对顺序。

reverse=True实现降序,等价于排序后再反转。但注意,对于字符串列表,直接排序默认按字典序;对于数字字符串像"10""9",结果可能不符合你按数值排的预期。此时需要key=int,先把字符串转成数字再比。

3.3 列表推导式与lambda:写代码的"快速通道"

列表推导式(list comprehension)是我个人认为Python最优雅的语法之一。它能把一个循环加一个条件判断压缩成一行表达式。

基本结构是:

[表达式 for 变量 in 可迭代对象 if 条件]

比如从0到9里挑出偶数并计算平方:

squares = [x**2 for x in range(10) if x % 2 == 0] # [0, 4, 16, 36, 64]

这行代码等价于:

squares = [] for x in range(10): if x % 2 == 0: squares.append(x**2)

可读性上,熟练之后推导式其实更清晰。但新手经常踩的坑是:为了"炫技"把推导式写得太复杂,比如嵌套两层for加两个if。这种时候可读性反而大幅下降。我的原则是推导式的复杂度控制在能一眼看懂的范围内,更复杂的逻辑就老老实实写普通循环。

lambda表达式常常和推导式、filtermapsorted这些函数式编程风格的写法混在一起用。比如list(map(lambda x: x * 2, lst)),把一个列表的每个元素翻倍。这种写法适合处理流式逻辑,但和列表推导式比起来,可读性并没有优势,反而多一层函数调用的开销。Python社区现在的审美倾向是:能用推导式就用推导式,lambda更多用在key参数这种不需要返回值名字的场合。

4. 元组:不可变带来的确定性

4.1 创建元组,别掉进单元素陷阱

元组的创建通常用圆括号:

t = (1, 2, 3)

也可以用tuple()构造函数把其他可迭代对象转成元组:

t1 = tuple([1, 2, 3]) t2 = tuple("abc") # ('a', 'b', 'c')

有一个细节必须提醒:创建单元素元组时,一定要加逗号。(1)在Python里只是整数1外面套了一个括号,而不是元组。正确写法是(1,)。空元组则可以直接用()

wrong = (1) right = (1,) print(type(wrong)) # <class 'int'> print(type(right)) # <class 'tuple'>

这个小陷阱看起来不起眼,实际写代码时却经常导致数据结构和预期不符。我在评审别人代码时见过不止一次,一个函数返回了(result),你以为你拿的是元组,对它做解包操作,结果报TypeError: cannot unpack non-iterable int object

4.2 元组解包:一行代码完成多变量赋值

元组最实用的特性之一就是支持解包(unpacking)。你可以把元组里的元素一次性赋给多个变量:

point = (3, 5) x, y = point print(x, y) # 3 5

这个特性让多返回值函数实现得非常自然。比如写一个函数,返回商和余数:

def divmod_pair(a, b): return a // b, a % b q, r = divmod_pair(17, 5) print(q, r) # 3 2

函数返回的是一个元组,调用方用解包语法直接拿到两个值,代码干净利落。Python官方内置的divmod()函数就是这种模式。

解包还有一些进阶用法。第一是交换变量,a, b = b, a一行就能完成,不需要中间临时变量,原理就是右边的b, a先构成一个元组再解包。第二是用*收集多余元素,比如first, *rest = (1, 2, 3, 4)rest会拿到[2, 3, 4]。第三是在循环里解包,比如遍历一个保存坐标的元组列表,for x, y in points:,提取字段非常直观。

4.3 元组的哈希特性:能当字典键的"身份证明"

元组不可变,所以它是可哈希的。也就是说,元组可以用来做字典的键,也可以放进集合里。这是列表做不到的,列表可变,不具备哈希能力。

d = {} d[(1, 2)] = "坐标一"

这个特性在需要把"多个字段组合成一个复合键"的场景下特别好用。比如统计二维坐标点出现的次数,(x, y)这个元组天然就可以作为字典的键。

但注意一个边界情况:如果元组内部包含可变元素,比如t = (1, [2, 3]),这时候这个元组不可哈希,因为[2, 3]可变,Python无法保证元组的哈希值稳定。你做hash(t)会直接抛TypeError。所以真正能当字典键的元组,必须是所有元素都不可变的那种。

Python标准库里还有一个非常有用的工具叫namedtuple,它能让元组字段具备名字。比如:

from collections import namedtuple Point = namedtuple("Point", ["x", "y"]) p = Point(3, 5) print(p.x, p.y) # 3 5

它本质上仍然是元组,支持索引、解包、不可变,但字段可以按名字访问,可读性远高于普通元组。用来表示一条固定结构的数据记录非常合适,比如配置项、坐标点、小型数据行。

5. 选型指南:什么时候用列表,什么时候用元组

5.1 性能与内存:元组真的比列表省吗

既然元组不可变,结构更简单,那么性能上确实有一定优势。元组的创建通常比列表略快,内存占用也更小,因为它不需要预留额外的扩容空间,也不存储与动态增删相关的元数据。一个只存几个元素的元组和一个列表相比,内存差异肉眼可见,但实际业务中不用过分纠结这点差距。

真正需要重视性能的场景是大量小对象。比如你有十万条坐标记录,每条用元组存,比用列表存整体省下不少内存。如果每条记录还要存进列表里做二次操作,那么"大列表套小元组"就是一种很常见的优化模式。

但是从日常开发角度看,选列表还是选元组,性能不是首要考虑因素,语义清晰才是。如果这个数据是一个会随着程序运行不断变化的集合,比如用户列表、日志列表,直接用列表;如果这个数据是一组固定不变的字段,比如一张表的行记录、函数的返回值,用元组更合适,还能避免被别人意外篡改。换句人话说:列表描述的是"我需要管理的数据",元组描述的是"我已经确定下来的数据"。

5.2 可变默认参数的经典陷阱

Python函数的默认参数有一个非常经典的反直觉设计:默认值在函数定义时只计算一次。如果你写def func(lst=[]),这个列表只创建一次,之后每次调用函数时,如果没有传入新列表,用的都是同一个列表对象。

def add_item(item, lst=[]): lst.append(item) return lst print(add_item(1)) # [1] print(add_item(2)) # [1, 2],而不是期望的 [2]

这就是可变默认参数陷阱。多个调用之间共享了同一个列表,数据互相污染。解决办法是用None作为默认值,在函数内部判断并创建新列表:

def add_item(item, lst=None): if lst is None: lst = [] lst.append(item) return lst

这个坑是所有Python面试基本都会考的。很多人觉得不就是一个默认参数嘛,但实际项目里因为这个bug产生的数据错乱非常难排查,因为每次调用看起来都"好像有点不对劲"但又不报错。我的建议很简单:默认参数永远不要用可变类型,统一用None占位。

5.3 浅拷贝与深拷贝:列表复制的坑

上一节我提过,lst2 = lst1只是把lst1的引用赋值给了lst2,两个名字指向同一个列表对象。这时候你改lst2lst1也会跟着变。

要复制内容,可以用切片lst1[:]或者list(lst1),这两个方法得到的是一个浅拷贝。浅拷贝的意思是:外层列表是新的,但列表里的元素还是原来那些对象的引用。如果元素本身是可变对象,那么修改内层对象,两个"拷贝"还是会被同时影响。

lst1 = [[1, 2], [3, 4]] lst2 = lst1[:] lst2[0].append(99) print(lst1) # [[1, 2, 99], [3, 4]]

lst2[0]lst1[0]指向的是同一个内层列表,所以内层变化会体现在两边。对于嵌套列表,你需要用copy模块的deepcopy

import copy lst1 = [[1, 2], [3, 4]] lst2 = copy.deepcopy(lst1) lst2[0].append(99) print(lst1) # [[1, 2], [3, 4]],不受影响

判断到底用浅拷贝还是深拷贝,核心就看列表里有没有可变的嵌套结构。如果只是存了一堆整数、字符串这种不可变对象,浅拷贝已经完全够用了。深拷贝虽然安全,但递归复制整个对象树,性能开销大,还可能遇到递归引用问题,不能无脑用。

6. 常见报错与排查技巧实录

6.1 高频报错速查表

以下是我在教学和项目里见到的、和列表元组相关的高频报错,整理成一张速查表,建议存下来:

报错信息出现原因排查方法
IndexError: list index out of range访问了不存在的索引位置检查索引是否在[-len(lst), len(lst)-1]范围内;先len(lst)看看实际长度;确认列表是否为空
ValueError: list.remove(x): x not in listremove删了一个不存在的值删除前先判断if x in lst,或用pop根据索引删
ValueError: tuple.index(x): x not in tupleindex查找的值不存在x in t先判断;或捕获ValueError异常
TypeError: 'list' object is not callable变量名把内置list函数覆盖了搜代码里有没有list = [...]这种命名,改掉变量名
TypeError: cannot unpack non-iterable int object对非序列类型做解包检查返回的到底是不是元组,确认没把单元素元组漏写逗号
TypeError: unhashable type: 'list'把列表当字典键或放进集合改用元组;或确保数据确实需要可变性
AttributeError: 'tuple' object has no attribute 'append'对元组调用列表的方法重新确认类型;如果确实需要修改,把元组转成列表list(t)

这张表里,unhashable type: 'list'可能让新手最摸不着头脑。它往往发生在写set(lst)去重的时候,但列表里的元素又恰好是列表。比如:

lst = [[1, 2], [1, 2]] set(lst) # TypeError: unhashable type: 'list'

这种情况就需要考虑你的数据设计是不是合理了。

6.2 列表去重:保序是个技术活

列表去重是高频需求,思路有很多,但在面试和实际编码中,顺序保持是一个经常被忽略的点。

最简洁的写法是list(set(lst))。它利用了集合的去重能力,但缺点是结果顺序不保证,大概率会和你原来的顺序不一样。如果顺序无所谓,这一行就够。如果要保持原顺序,常见方案有两种。

第一种是用dict.fromkeys(),利用字典键的唯一性和Python 3.7+字典保持插入顺序的特性:

lst = [3, 1, 3, 2, 1, 4] deduped = list(dict.fromkeys(lst)) # [3, 1, 2, 4]

第二种是循环加set判断:

seen = set() result = [] for x in lst: if x not in seen: seen.add(x) result.append(x)

两种方法都能保序,第一种代码更短,第二种更容易理解。如果列表元素是不可哈希的可变对象,只能走第二种,并配合自定义的相等判断逻辑。

还有一个反直觉的坑:lst.remove(x)只删除第一个匹配项。如果你想删除所有等于x的元素,可以用前面提到的列表推导式重新构造列表:lst = [i for i in lst if i != x]。这种"筛选重建"的方式处理批量删除非常干净,特别推荐。

6.3 嵌套引用与数据污染的排查思路

如果代码逻辑看起来没错,但数据总被莫名其妙修改,十有八九是引用共享问题。我最常用的排查路径是:先想清楚这条数据是从哪来的——是函数返回的?还是从别的列表切片得到的?还是直接赋值的?然后检查是否有多个变量名指向同一个可变对象。

一个非常有效的调试工具是id()函数,它能返回对象的内存地址标识。你可以比较几个变量名的id,如果完全一致,说明它们引用同一个对象。另外也可以用is判断,比如lst1 is lst2返回True意味着它们就是同一个列表。

这个排查思路对列表、字典、集合所有可变类型都适用。一旦定位到是引用共享问题,解决方案就是回到5.3节:确认用浅拷贝还是深拷贝,把数据彻底"切开"。

7. 综合实战:处理一个浮点数列表的入门经典题

7.1 真实场景:从py202.py出发的浮点数列表处理

网上流传的Python考试题里有一类特别典型,它说"在考生文件夹下有个文件py202.py,定义了一个6个浮点数的一维列表l1和一个包含3个子列表的二维列表",然后要求做各种统计、排序、筛选。这类题看起来是考试题,其实考察的全是列表和元组最核心的知识点。

我把它稍微改编成一个更有实战感的场景:假设你现在拿到一组温度采样数据,里面既有单日温度,也有按周分组的历史温度记录,需要你完成几件事:

  1. 计算单日温度列表的最大值、最小值和平均值。
  2. 用切片取出前三天的温度,以及最后一天的温度。
  3. 对温度列表做升序排序,并保留原始数据的备份。
  4. 把每个周组的平均温度算出来,存成一个包含(周次, 平均温度)形式的元组列表,再按平均温度降序排列。

这几件事做下来,列表的遍历、切片、统计、排序、元组打包、解包、按key排序这些知识点就全练到了。

7.2 代码实现:把列表切片、排序、元组打包一起用

我直接给一份可运行的参考代码:

import random # 模拟6个浮点数的一维列表 l1 = [36.5, 37.1, 36.8, 37.3, 36.9, 37.0] # 1. 统计最大值、最小值、平均值 max_temp = max(l1) min_temp = min(l1) avg_temp = sum(l1) / len(l1) print(f"最高温: {max_temp}, 最低温: {min_temp}, 平均温: {avg_temp:.2f}") # 2. 切片:前三天和最后一天 first_three = l1[:3] last_day = l1[-1] print(f"前三天温度: {first_three}, 最后一天温度: {last_day}") # 3. 排序:保留原始数据,用 sorted 返回新列表 sorted_l1 = sorted(l1) print(f"排序后: {sorted_l1}") print(f"原列表: {l1}") # 4. 模拟二维列表:3个周组,每周7个温度 weekly = [ [36.6, 37.0, 36.7, 36.9, 37.2, 36.8, 37.1], [37.3, 36.5, 36.8, 37.0, 36.6, 36.9, 37.2], [36.9, 37.1, 37.0, 36.7, 36.8, 37.3, 36.5], ] # 计算每个周组的平均温度,打包成元组列表 weekly_avg = [] for i, week_temps in enumerate(weekly, start=1): avg = sum(week_temps) / len(week_temps) weekly_avg.append((i, avg)) # (周次, 平均温度) # 按平均温度降序排列 weekly_avg.sort(key=lambda item: item[1], reverse=True) for week_no, avg in weekly_avg: print(f"第{week_no}周平均温度: {avg:.2f}")

这段代码里用到的技巧都很典型。maxminsum都是Python内置函数,直接操作列表;f"{avg:.2f}"是格式化字符串的写法,保留两位小数。sorted创建一个新列表,原列表l1不受影响,这也回应了5.3的引用问题。enumerate在遍历二维列表时同时取周次和温度列表,start=1让周次从1开始计数。最后weekly_avg.sort(key=lambda item: item[1], reverse=True)就是3.2节讲的按元组第二项排序的具体应用。整个流程下来,列表和元组在同一个真实场景里各司其职:列表承载动态数据,元组记录固定结构。

7.3 扩展想法:给新手的三点建议

运行上面的代码之后,我建议你再动手做三个小变形,把知识点真正内化:

第一,把weekly_avg改成用列表推导式生成,试着用一行代码算出所有周的平均温度元组。第二,把week_temps的数据类型从列表改成元组,看看程序哪里会报错,体会一下元组不可变带来的限制。第三,给这段代码加一个输入入口,让你可以手动录入一周温度并实时计算均值,这样就把输入输出和数据处理串起来了。

我自己的体会是,列表和元组的难点不在语法,而在"什么时候用谁"。多写几个像这样的小程序,遇到报错后亲自查一次id()、亲手做一次浅拷贝和深拷贝对比,你对它们的理解就能超越绝大多数只会背语法的人。后面你学字典、集合时也会发现,很多坑的根源依然是可变与不可变、引用与拷贝这两条线。把这两条线吃透了,Python数据结构这块的地基就算真正打牢了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询