资讯动态

Python 第 03 讲:列表 list 与元组 tuple——操作、引用、排序与复杂度

发布时间:2026/10/20 10:47:53 来源:尧图企业网站定制
本讲目标会使用列表和元组能判断常见代码的运行结果理解修改与复制的区别知道什么时候应选择其他容器。示例基于 Python 3。前一讲介绍了字符串str字符串中的字符不能直接修改。本讲的列表与元组也能按位置访问元素但列表可变元组不可变。这是后续所有差别的起点。一、先弄清楚列表和元组是什么列表可以理解成一排带编号的格子能读取、替换、添加和删除内容。元组也有编号但创建后不能替换其中格子指向的对象也不能增减格子。angles [10, 20, 30] # 列表 point (10, 20, 30) # 元组 angles[1] 45 print(angles) # [10, 45, 30] print(point[1]) # 20如果执行point[1] 45会发生TypeError。两个容器都能存储不同类型、重复元素均支持len()、索引、切片和for遍历。单元素元组要特别注意逗号a (10) # int b (10,) # tuple print(type(a), type(b))什么时候用内容数量和顺序可能变化例如连续采集的角度适合列表结构固定的数据例如(x, y, z)常用元组。这是设计习惯不是强制规定。二、索引、切片和最常用的增删改查angles [10, 20, 30, 40] print(angles[0]) # 10第一项 print(angles[-1]) # 40最后一项 print(angles[1:3]) # [20, 30]包含1不包含3 print(angles[::-1]) # [40, 30, 20, 10]注意单个索引越界会报IndexError而普通切片超出边界通常不会报错。几个方法放在同一段代码中观察angles [10, 20, 30] angles.append(40) # 末尾添加一个元素 angles.extend([50, 60]) # 把两个元素依次加入 angles.insert(1, 15) # 在索引1前插入 print(angles) # [10, 15, 20, 30, 40, 50, 60] removed angles.pop() # 删除并返回最后一项 angles.remove(20) # 删除第一个值为20的元素 print(removed) # 60 print(angles) # [10, 15, 30, 40, 50]最容易混淆的地方是操作含义示例结果append([3, 4])把整个列表作为一个元素添加[1, 2, [3, 4]]extend([3, 4])依次添加其中的元素[1, 2, 3, 4]remove(20)按值删除第一个匹配项返回Nonepop(1)按位置删除并返回该项返回删除的对象append()、extend()、insert()、remove()、sort()等原地修改方法通常返回None不要写成angles angles.append(40)否则angles会变成None。三、数据结构为什么会影响速度还是用“带编号的格子”来理解列表能够通过编号直接找到某一格因此angles[2]很快。列表内部的格子在常见的 CPython 实现中连续存放着对象引用引用可以粗略理解成“指向真实对象的线索”。列表本身[位置0] [位置1] [位置2] | | | 10 20 30 分别指向这些对象在列表末尾append(40)通常只要把一个新引用放到末尾如果空间不够解释器会扩容所以大量追加时平均每次很快。但是insert(0, 5)要让原来的元素依次往后挪因此元素越多操作通常越慢。用大 O 表示“数据量变大时工作量增长得有多快”操作通常复杂度直观原因a[i]、a[i] xO(1)通过编号定位a.append(x)均摊 O(1)一般直接在末尾添加偶尔扩容a.insert(0, x)O(n)许多元素需要右移a.pop()均摊 O(1)取出末尾元素a.pop(0)O(n)后面的元素要左移x in aO(n)可能要逐个查找a.copy()O(n)复制一排引用a.sort()最坏 O(n log n)需要比较、排列元素n表示列表元素数量。这里只要记住索引和尾部操作通常快频繁头部操作和逐项查找成本更高。复杂度不是精确运行时间。四、最容易出错赋值不是复制a [10, 20] b a b[0] 99 print(a) # [99, 20] print(a is b) # Trueb a没有重新创建列表两个名字共用同一份列表。可以把它理解成两个标签贴在同一个盒子上。如果只想复制一层a [10, 20] b a.copy() b[0] 99 print(a) # [10, 20] print(b) # [99, 20]但遇到“列表里还有列表”时copy()仍有陷阱a [[1, 2], [3, 4]] b a.copy() b[0][0] 99 print(a) # [[99, 2], [3, 4]]因为copy()只复制外层列表内部两行仍被共享。若确实需要让嵌套列表也各自独立可用copy.deepcopy()import copy a [[1, 2], [3, 4]] b copy.deepcopy(a) b[0][0] 99 print(a) # [[1, 2], [3, 4]]还有一个经典错误bad [[0] * 3] * 2 bad[0][0] 9 print(bad) # [[9, 0, 0], [9, 0, 0]] right [[0] * 3 for _ in range(2)] right[0][0] 9 print(right) # [[9, 0, 0], [0, 0, 0]]bad中的两行其实是同一个内部列表推导式每次生成一行才得到相互独立的二维列表。五、排序、遍历和元组的特殊之处排序最需要分清“改原列表”还是“返回新列表”a [30, -10, 20] b sorted(a) # 得到新列表 print(a) # [30, -10, 20] print(b) # [-10, 20, 30] a.sort(keyabs, reverseTrue) print(a) # [30, 20, -10]按绝对值降序sort()修改原列表返回Nonesorted()返回新列表。Python 的排序是稳定的当两项排序依据相等时会保留它们原本的先后顺序。需要同时获取编号和数值时用enumerate()angles [10, 20, 30] for index, angle in enumerate(angles, start1): print(fJ{index}: {angle})输出J1: 10 J2: 20 J3: 30要把多个序列配对可以用zip()names [J1, J2, J3] angles [10, 20, 30] print(list(zip(names, angles))) # [(J1, 10), (J2, 20), (J3, 30)]元组不可变但元组中的列表仍然可变t ([1, 2], 3) t[0].append(4) print(t) # ([1, 2, 4], 3)原因是元组没有更换所引用的内部列表只是那个内部列表的内容发生了变化。带有列表等不可哈希元素的元组也不能直接用作字典键。六、编程训练3题含标准答案第1题关节角度限幅输入[200, -190, 45, 0]把每个角度限制在[-180, 180]返回新列表不得修改原列表。标准答案def clamp_angles(angles, low-180, high180): if low high: raise ValueError(下限不能大于上限) return [max(low, min(x, high)) for x in angles] angles [200, -190, 45, 0] result clamp_angles(angles) print(result) # [180, -180, 45, 0] print(angles) # [200, -190, 45, 0]解析min(x, high)保证不超过上限max(low, ...)保证不低于下限推导式创建新列表。时间 O(n)额外空间 O(n)。第2题计算每个关节的平均角度给定三帧数据每帧包含三个关节角度计算每一列的平均值。frames [[0, 30, 60], [10, 40, 80], [20, 50, 100]]期望输出[10.0, 40.0, 80.0]。标准答案def joint_averages(frames): if not frames: return [] joint_count len(frames[0]) if any(len(row) ! joint_count for row in frames): raise ValueError(每一帧的关节数量必须一致) totals [0.0] * joint_count for row in frames: for index, angle in enumerate(row): totals[index] angle return [total / len(frames) for total in totals] frames [[0, 30, 60], [10, 40, 80], [20, 50, 100]] print(joint_averages(frames)) # [10.0, 40.0, 80.0]解析每次内层循环都把同一关节位置的数值累加。若有f帧、每帧j个关节时间为 O(fj)额外空间 O(j)。第3题去除连续重复状态输入frames [[0, 0], [0, 0], [0, 30], [0, 30], [10, 30]]将连续相同的状态合并成(状态元组, 连续次数)期望[((0, 0), 2), ((0, 30), 2), ((10, 30), 1)]标准答案def compress_frames(frames): if not frames: return [] joint_count len(frames[0]) result [] current tuple(frames[0]) count 1 for frame in frames[1:]: if len(frame) ! joint_count: raise ValueError(关节数量不一致) state tuple(frame) if state current: count 1 else: result.append((current, count)) current state count 1 result.append((current, count)) return result frames [[0, 0], [0, 0], [0, 30], [0, 30], [10, 30]] print(compress_frames(frames))解析每遇到不同状态保存上一组并重新计数循环结束后必须再保存最后一组。若有f帧、每帧j个关节时间 O(fj)最坏输出空间 O(fj)。七、本讲要记住的结论list可变tuple不可变但元组里的可变对象仍可能被修改。列表底层可理解成“连续排列的对象引用槽位”所以按索引访问快、头部插入慢。append()添加一个对象extend()添加可迭代对象中的各项。a b共享列表copy()只复制一层嵌套数据需要根据实际需求判断是否使用deepcopy()。sort()改原列表、返回Nonesorted()返回新列表。二维列表要避免[[0] * m] * n造成各行引用同一个内部列表。

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

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

免费获取报价 →
↑