前三篇我们分别讲了常量/变量/运算符、条件与循环、函数。本篇把容器三件套——列表 list、元组 tuple、字典 dict——一次性讲透:不只是「怎么用」,更讲清「为什么这么设计、底层怎么存、面试会被怎么追问」。
一、引子:为什么需要容器
写代码经常会遇到这种情况:要存一组数、一组对象,甚至数量都不确定。
num1 = 10 num2 = // ... 复制粘贴二十遍
如果数据少,多个变量也凑合;但数据一多(比如一个班级的成绩、一个接口返回的字段列表),逐个变量就崩了。你需要一种「批量装数据」的方式——这就是容器。
python 里最常用三种:列表装可变序列、元组装不可变序列、字典装键值映射。它们也是后面写爬虫、写后端、写数据处理绕不开的基础。
二、列表 list:可变的序列
2.1 创建与本质
a = [] # 空列表 a = list() # 等价写法 a = [1, 2, 3, 4] a = [1, 'hello', true] # 元素可以是不同类型(和 c/c++ 数组差别巨大)
要点: python 的 list 不要求元素类型一致。底层其实是一个「指向对象的指针数组」(pyobject* 数组),所以能塞任意类型。这一点和 c 的 int arr[10]、c++ 的 vector<int> 都不同——后者类型固定。
2.2 下标与切片
a = [1, 2, 3, 4] print(a[2]) # 3,下标从 0 开始 print(a[-1]) # 4,负数表示倒数第几个 print(a[1:3]) # [2, 3],前闭后开区间 [1, 3) print(a[1:]) # [2, 3, 4],省略后边界 print(a[::-1]) # [4, 3, 2, 1],步长为负数=倒序
边界细节:
- 下标越界会抛
indexerror,但切片越界不会报错,能取多少取多少——这和 c/c++ 完全不同,新手容易误以为切片也越界崩溃。 - 负数步长
a[::-1]是「不创建新逻辑、只改遍历方向」的语法糖,常用于反转列表。
2.3 增删改查
a = [1, 2, 3, 4]
a.append('hello') # 尾插,o(1) 平均
a.insert(1, 'x') # 指定位置插入,o(n)
print(2 in a) # true,in 判断是否存在
print(a.index(2)) # 1,找不到抛 valueerror
a.pop() # 删末尾,o(1)
a.pop(2) # 按下标删,o(n)
a.remove(2) # 按值删,o(n)(先找后删)
方法 vs 函数: append 是「方法」——它依附于 a 这个对象(a.append),不是独立函数。这是面向对象「消息发送」的写法,和之前学的独立函数 len(a) 不同。
2.4 连接
a = [1, 2, 3, 4] b = [5, 6, 7] print(a + b) # 新列表 [1,2,3,4,5,6,7],a/b 不变 a.extend(b) # 把 b 拼到 a 末尾,修改 a,不改 b
+ 生成新对象,extend 原地修改——这是「要新列表还是改原列表」的关键取舍。
2.5 面试官会怎么考
append和insert复杂度为什么差这么多? 答:list 是动态数组,尾插只需在末尾放元素;insert(i)要把 i 之后的元素整体后移,最坏 o(n)。remove和pop哪个快?pop()删末尾 o(1),pop(i)/remove都要搬移,o(n)。- 切片是深拷贝还是浅拷贝? 切片(如
a[:])创建新列表,但元素是「引用拷贝」——元素是对象时,新旧列表指向同一批对象(浅拷贝)。这是经典坑,后续讲深拷贝再展开。
三、元组 tuple:不可变的序列
3.1 创建与不可变
t = ()
t = tuple()
t = (10, 20)
def getpoint():
return 10, 20 # 实际上返回的就是元组
result = getpoint()
print(type(result)) # <class 'tuple'>
为什么有了列表还要元组? 两个核心原因:
- 安全: 把数据交给一个函数处理,你不确定它会不会把你的数据弄乱。传元组,对方改不了,安心。
- 能当字典的键: 字典的键必须是「可哈希(不可变)」的对象,列表不行,元组可以。
3.2 支持的操作
元组支持读操作:下标、切片、遍历、in、index、+。不支持写操作:不能改元素、不能增删、extend 也没有。
t = (1, 2, 3, 4) print(t[0], t[1:3], 2 in t) # 这些都能用 # t[0] = 100 # 报错:元组不可修改
3.3 面试官会怎么考
- 「函数为什么会返回元组?」 答:python 用逗号
,打包返回多个值,本质就是元组;用a, b = f()解包,或_忽略不想要的返回值(如_, b = getpoint())。 - 「元组和列表怎么选?」 数据不需要变 → 优先元组(安全、可作 key);需要增删改 → 列表。
四、字典 dict:键值对映射
4.1 创建与本质
d = {}
d = dict()
student = {'id': 1, 'name': 'zhangsan'}
student = {
'id': 1, # 末尾逗号可写可不写
'name': 'zhangsan'
}
字典的本质是哈希表(hash table)。键 key 和值 value 一一映射,靠 key 快速查 value——类比「学号→学生」。
4.2 查 / 增 / 改 / 删
print('id' in student) # true
print(student['id']) # 1,key 不存在抛 keyerror
student['score'] = 90 # key 不存在→新增;存在→修改
student.pop('score') # 按 key 删
要点: student['score'] = ... 这行既是新增也是修改,「存在就看赋值、不存在就插入」,记住这一个规则即可。
4.3 遍历
for key in student: # 直接遍历拿到 key
print(key, student[key])
print(student.keys()) # dict_keys,拿到所有 key
print(student.values()) # dict_values,拿到所有 value
print(student.items()) # dict_items,拿到所有 (key, value)
️4.4合法的 key 类型
print(hash(0)) # 能算哈希 → 可作 key
print(hash('hello'))
print(hash(())) # 空元组也能算哈希
# print(hash([1, 2, 3])) # 报错:列表不可哈希
# print(hash({'id': 1})) # 报错:字典也不可哈希
为什么限制 key 必须是可哈希? 哈希表靠 hash(key) 定位桶位,如果 key 能变,定位就失效了。所以「可变对象(list、dict)」统统不能当 key,而「不可变对象(int、str、tuple)」可以。
4.5 面试官会怎么考
- 「dict 查找是 o(1) 吗?」 答:平均 o(1),但哈希冲突严重时退化;最坏情况(全冲突)理论 o(n),工程中靠扩容和好哈希规避。
- 「list 和 dict 都能存多个数据,怎么选? 答:要「按位置取」用 list;要「按名字/id 取」用 dict。dict 的语义是映射,list 的语义是序列。
- 「哈希冲突怎么办?」 答:开放寻址或链地址;python 用开放寻址,负载因子超阈值就 rehash(重建表),这也解释了为什么 dict 频繁插入会偶发卡顿。
五、三者横向对比(一张表看懂)
| 维度 | 列表 list | 元组 tuple | 字典 dict |
|---|---|---|---|
| 可变性 | 可变(能增删改) | 不可变 | 键值可变,但 key 必须不可变 |
| 底层 | 动态数组 | 不可变数组 | 哈希表 |
| 典型用途 | 未知数量、需修改的序列 | 固定数据、函数多返回值、作 key | 按 key 快速查值 |
| 下标/切片 | 支持 | 支持 | 不支持(用 key 取) |
| 能否作 dict key | 不能 | 能 | 不能 |
| 查找复杂度 | o(n)(in)/ o(1)(下标) | 同左 | 平均 o(1)(按 key) |
一句话选型: 需要改 → 列表;固定不变 → 元组;要按名字查 → 字典。
六、常见陷阱与误区
- 切片越界不报错:
a[100:200]越界只取能取的,新手常以为会崩——和a[100]下标越界抛异常不同,别混。 - 方法 vs 函数:
a.append(x)是方法(依附对象),len(a)是函数(独立)。概念不清会导致「为什么有的有括号有的没有」的困惑。 - 元组「不可变」的边界:元组里的元素不能换,但如果元素本身是可变对象(如 list),元素内部仍可改——元组只保证「指向不变」,不保证「内容不变」。
+与extend的副作用:+不改原列表,extend改原列表,写并发/共享数据时要小心。
七、小结 & 下一步
列表、元组、字典是 python 日常最高频的三种容器:
- 列表是「可变的动态数组」,下标/切片/增删改查全套,记住尾部操作的复杂度差异;
- 元组是「不可变的序列」,安全、能作字典键、天然适合多返回值;
- 字典是「哈希表」,围绕 key 做映射,key 必须可哈希。
这三者和前三篇的函数、循环配合起来,就能写出真正有结构的程序——比如用 dict 存学生信息、用 list 收集结果、用 tuple 返回多值。下一阶段建议往两个方向走:一是字符串与文件操作(把数据真正落地),二是面向对象与类(理解「方法」背后的对象模型)。先把这三件套练熟,后面学 pandas、写后端都不虚。
到此这篇关于python列表、元组、字典从创建到底层的文章就介绍到这了,更多相关python列表、元组、字典内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论