跳到主要内容

内置容器、切片、解包与复制语义

本节目标

查询内置容器、切片、解包、可变性和复制。

选择容器时,先确定数据按位置、键还是唯一性组织,再考虑是否需要修改。本章把上一章的对象、名称与可变性模型用于列表、元组、字典和集合,并沿着索引、切片、解包与复制追踪新对象和共享引用。第一次接触这些容器时,可从任务地图直接建立选择依据。

内置容器任务地图

“数据靠什么被找到”通常是选择容器的第一问;同一份业务数据在不同处理阶段也可能采用不同表示。

任务特征常用容器关键性质
按位置保存、允许重复且需要增删list有顺序、可变的序列
按位置保存、结构固定tuple有顺序、不可变的序列
表示一段按步长生成的整数range不可变的整数序列表示
按键查找值dict保留插入顺序的可变映射
去重、成员判断或集合运算set可变、元素唯一且必须可哈希
需要不可变的集合值frozenset不可变、元素唯一且必须可哈希

表中的“有顺序”只表示迭代或按位置观察时存在规定的先后关系,不代表内容已按大小或业务含义排序。是否允许重复、是否按键访问,以及容器自身是否需要修改,仍要分别判断。

列表与可变序列操作

列表字面量用方括号创建,例如 names = ["Ada", "Guido"]。列表是可变序列:append(value) 在末尾追加一个元素,extend(iterable) 逐个追加可迭代对象产生的元素,insert(index, value) 在指定位置插入;下标赋值、切片赋值和 del 也会修改原列表。

列表方法不能一概视为“返回修改后的列表”。items.append(value)items.extend(values)items.sort() 都修改原对象并返回 None,所以 items = items.append(value) 会把名称改绑到 None。相比之下,items + other 和普通列表切片会创建新列表。遍历期间改变列表长度可能跳过元素;筛选操作应先明确结果容器,推导式语法留待后续章节。

列表保存的是对象引用,而不是把每个对象递归嵌入一份。于是列表可变并不代表元素都可变,列表不可作为集合元素或字典键,也不代表从列表得到的所有副本都会递归复制元素。

元组、逗号与 range

元组由逗号形成,括号主要用于分组:pair = 10, 20pair = (10, 20) 都产生二元组。单元素元组必须保留逗号,写作 (10,)(10) 只是整数表达式。元组不可变,不能替换、插入或删除其中的位置,但元组中的引用仍可能指向可变对象;修改那个对象不等于修改元组的元素绑定。

range(stop)range(start, stop)range(start, stop, step) 表示按规则产生的整数序列,终点 stop 不包含在内,step 不能为零。range(1, 7, 2) 依次表示 1、3、5range 本身不可变,也不会为了表示很大的区间而预先建立一张完整整数列表;需要列表时可显式调用 list(...)

位置数量和含义固定的记录可用元组,整数区间可用 range,需要更新的通用序列则通常使用列表。元组和 range 的不可变性只约束容器自身提供的更新操作,不会递归传递给所有成员。

索引、负索引与切片

序列索引从 0 开始。对长度为 n 的序列,有效非负索引是 0n - 1;负索引从末尾计数,-1 是最后一个元素,-n 是第一个元素。单个索引超出有效范围会抛出 IndexError,空序列没有任何有效索引。

切片 sequence[start:stop:step] 使用半开区间,包含 start 所指位置而不包含 stop。在默认正步长下,numbers[1:3][10, 20, 30, 40] 得到 [20, 30],边界也可省略为 numbers[:2]numbers[2:]。超出范围的切片边界会被调整到可用范围,因此 numbers[:100] 合法并取得全部元素;单个索引越界则会报错。

step 控制间隔和方向,但不能为零。负步长既反转行进方向,也改变省略边界的默认值,例如 numbers[::-1] 会得到逆序的新列表。列表切片虽然创建新外层容器,其中保存的仍是原有元素引用;字符串和元组切片则返回相应序列类型。新切片不等于成员已被递归复制。

字典、键与插入顺序

字典把可哈希的键映射到值,例如 metadata = {"language": "python", "version": "3.14"}metadata["language"] 读取已有键;对缺失键使用方括号会抛出 KeyError。只想提供缺失时的默认值可用 metadata.get("status", "unknown"),而赋值 metadata["status"] = "draft" 会新增或更新条目。

迭代字典键时可依赖插入顺序:更新已有键不会改变它的位置,删除后重新加入则形成一次新插入。这项保证仅覆盖条目的观察次序,不表示键已按字母、数值或业务含义排列。需要特定顺序时,应显式调用 sorted(...) 并提供合适依据。

键必须可哈希,并且在参与哈希期间保持适合相等比较的稳定状态。字符串、整数以及成员均可哈希的元组常可作键,列表和字典不可作键。值没有这个限制,可以是列表等可变对象。

set、frozenset 与集合运算

set 保存互不重复的可哈希元素,适合成员判断、去重和集合关系运算。left | right 求并集,left & right 求交集,left - right 求差集,left ^ right 求对称差集。空集合必须写 set(){} 创建的是空字典。向集合加入列表会因列表不可哈希而抛出 TypeError

set 提供 add()remove()discard() 等修改操作;frozenset 不可变,在成员均满足要求时还可作为字典键或另一个集合的元素。两者表达集合关系,均不承诺可依赖的元素迭代顺序。某次运行中看似固定的打印次序,不应进入协议、测试或教材结论。

稳定展示集合时,可以先用 sorted(topics) 取得列表。排序本身要求元素之间存在合适关系;若混合类型不能直接比较,就要定义业务排序键。本文的代表脚本同样只输出排序后的集合值。

打包、解包与星号目标

逗号可以把多个值打包成元组,也可以让赋值目标按位置解包:x, y = 10, 20 先产生右侧值,再分别绑定名称,因此 x, y = y, x 可以交换绑定。没有星号目标时,目标数量必须与输入元素数量完全相同,否则抛出 ValueError

一个赋值目标列表中最多有一个星号目标。head, *middle, tail = [10, 20, 30, 40]head10middle 为新列表 [20, 30]tail40。星号目标可以接收零个元素,但其余非星号目标仍必须得到值;输入过短仍会抛出 ValueError

解包逐个接收输入产生的对象引用,不会递归复制它们;嵌套目标还要求输入具备对应的嵌套结构。函数调用也使用 *iterable**mapping 展开数据,相关参数绑定规则将在函数章节说明。

别名、浅复制与嵌套共享

赋值 alias = original 只增加一个指向同一对象的名称,alias is original 为真;通过任一名称修改这个可变对象,另一名称都会看到变化。outer_copy = original.copy() 对列表创建一个新的外层列表,所以两个外层列表身份不同,但新列表中的元素仍是原列表所引用的那些对象。

元素本身是可变容器时,修改共享的内层对象会同时反映在两个外层列表中。浅复制的准确含义是复制一层容器结构、复用成员引用,而非递归复制;.copy()、切片或 list(original) 都不能证明嵌套结构已经彼此独立。只有确实需要隔离全部嵌套状态时,才有必要设计递归构造或评估深复制的语义。

运行代表脚本时,可以依次观察切片与解包、字典插入顺序、排序后的集合输出,以及浅复制后仍被两个外层列表共享的内层列表。

container_copy_report.py
numbers = [10, 20, 30, 40]
head, *middle, tail = numbers
metadata = {"language": "python", "version": "3.14"}
topics = {"typing", "python"}
nested = [["draft"]]
outer_copy = nested.copy()
outer_copy[0].append("review")

print(f"slice={numbers[1:3]}")
print(f"unpacked=head:{head},middle:{middle},tail:{tail}")
print(f"dict-order={list(metadata)}")
print(f"set-values={sorted(topics)}")
print(f"outer-copy-shared={nested[0] is outer_copy[0]}")
print(f"inner-values={nested[0]}")
slice=[20, 30]
unpacked=head:10,middle:[20, 30],tail:40
dict-order=['language', 'version']
set-values=['python', 'typing']
outer-copy-shared=True
inner-values=['draft', 'review']

脚本中的 nested is outer_copy 为假,而 nested[0] is outer_copy[0] 为真。对 outer_copy[0] 的追加修改的是共享内层列表,所以从 nested[0] 也能读到 "review";这不是两个独立内层列表自动同步。

容器选择、复制与遍历边界

  • 混淆索引与切片边界:单个越界索引抛出 IndexError,越界切片边界通常被调整;切片终点仍不包含在结果中。
  • 假定字典自动按键排序:字典保留插入顺序,不承诺任意业务排序;需要排序时显式表达排序依据。
  • 直接展示集合并依赖其顺序:setfrozenset 不提供稳定迭代顺序;固定输出应先 sorted(...)
  • 用方括号读取可能缺失的字典键:它会抛出 KeyError;根据业务需要选择成员检查、get(),或明确处理异常。
  • 把不可哈希对象作为键或集合元素:列表、字典和集合本身不可哈希,会触发 TypeError;不可变外观也不自动保证成员都可哈希。
  • 把别名或浅复制误认为递归独立副本:alias = original 连外层也共享,浅复制只分离外层,嵌套的可变对象仍可能共享。
  • [[]] * count 创建嵌套列表后假定每个内层独立:重复的是同一个内层引用,一处修改会出现在多个位置;应逐个创建确实独立的成员。
  • 解包时忽略长度约束:没有星号目标时数量必须完全匹配;有一个星号目标也要为其他目标保留足够元素,否则抛出 ValueError

本章只建立容器本身与复制边界,不讲列表、集合或字典推导式;该主题会在相应章节发布后单独链接。继续阅读运算符、表达式、条件、循环与模式匹配,学习容器如何参与表达式、分支与迭代控制。