
本篇把容器三件套——列表 list、元组 tuple、字典 dict——一次性讲透:不只是「怎么用」,更讲清「为什么这么设计、底层怎么存、面试会被怎么追问」。
一、引子:为什么需要容器
写代码经常会遇到这种情况:要存一组数、一组对象,甚至数量都不确定。
num1 = 10num2 = // ... 复制粘贴二十遍
如果数据少,多个变量也凑合;但数据一多(比如一个班级的成绩、一个接口返回的字段列表),逐个变量就崩了。你需要一种「批量装数据」的方式——这就是容器。
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,找不到抛 ValueErrora.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) # Trueprint(student['id']) # 1,key 不存在抛 KeyErrorstudent['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,拿到所有 keyprint(student.values()) # dict_values,拿到所有 valueprint(student.items()) # dict_items,拿到所有 (key, value)
4.4合法的 key 类型
print(hash(0)) # 能算哈希 → 可作 keyprint(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 频繁插入会偶发卡顿。
五、三者横向对比(一张表看懂)
一句话选型: 需要改 → 列表;固定不变 → 元组;要按名字查 → 字典。
六、常见陷阱与误区
切片越界不报错:a[100:200] 越界只取能取的,新手常以为会崩——和 a[100] 下标越界抛异常不同,别混。
方法 vs 函数:a.append(x) 是方法(依附对象),len(a) 是函数(独立)。概念不清会导致「为什么有的有括号有的没有」的困惑。
元组「不可变」的边界:元组里的元素不能换,但如果元素本身是可变对象(如 list),元素内部仍可改——元组只保证「指向不变」,不保证「内容不变」。
+ 与 extend 的副作用:+ 不改原列表,extend 改原列表,写并发/共享数据时要小心。
七、小结 & 下一步
列表、元组、字典是 Python 日常最高频的三种容器:
列表是「可变的动态数组」,下标/切片/增删改查全套,记住尾部操作的复杂度差异;
元组是「不可变的序列」,安全、能作字典键、天然适合多返回值;
字典是「哈希表」,围绕 key 做映射,key 必须可哈希。
这三者和前三篇的函数、循环配合起来,就能写出真正有结构的程序——比如用 dict 存学生信息、用 list 收集结果、用 tuple 返回多值。下一阶段建议往两个方向走:一是字符串与文件操作(把数据真正落地),二是面向对象与类(理解「方法」背后的对象模型)。先把这三件套练熟,后面学 pandas、写后端都不虚。
以上就是“Python 列表、元组、字典:从创建到底层,一文搞懂!”的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。
扫码二维码 获取免费视频学习资料

- 本文固定链接: http://www.phpxs.com/post/14618/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料