🐍 Day3 : Python 容器精讲 — list、dict、set、tuple 底层实现与高级操作

学习路径:Python_AIGC · Day 3

知识点简介

Python 四大内置容器(list、dict、set、tuple)几乎出现在每一段 Python 代码中。但很多人只用了它们 20% 的功能。

今天不讲「list 怎么追加元素」这种基础,而是从 底层实现 → 高级操作 → 性能对比 → 避坑指南 的路径走一遍,让你真正理解每个容器的「性格」。


1️⃣ list --- 动态数组

底层实现

Python list 底层是 动态数组(Dynamic Array),不是链表:

css 复制代码
底层结构示意:
[obj1][obj2][obj3][obj4][ 空 ][ 空 ]
  ↑                          ↑
已占用(4个)              预留容量
  • 尾部追加 O(1) 摊还 ------ 因为空间不够时自动扩容(约 1.125 倍)
  • 中间插入/删除 O(n) ------ 需要挪动后续元素
  • 随机访问 O(1) ------ 直接通过索引计算内存地址

高级操作

python 复制代码
# 切片赋值(批量替换)
nums = [1, 2, 3, 4, 5]
nums[1:4] = [20, 30]       # → [1, 20, 30, 5]
nums[1:1] = [1.5]          # 插入 → [1, 1.5, 20, 30, 5]

# 列表推导 + 条件过滤
squares = [x**2 for x in range(10) if x % 2 == 0]   # [0, 4, 16, 36, 64]

# 嵌套推导展平
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [x for row in matrix for x in row]             # [1, 2, 3, 4, 5, 6]

# bisect 二分查找(有序列表)
import bisect
scores = [60, 70, 80, 90]
pos = bisect.bisect_left(scores, 75)   # → 2(插入位置)
bisect.insort(scores, 75)              # 插入并保持有序

# `*` 解包操作符
first, *rest = [1, 2, 3, 4, 5]        # first=1, rest=[2,3,4,5]

避坑指南

python 复制代码
# ⚠️ 复制陷阱
a = [[1], [2], [3]]
b = a * 3                     # 浅拷贝!内部列表是同一引用
b[0][0] = 999
print(a)                      # [[999], [2], [3]] --- 被改了!

# ✅ 正确复制
import copy
c = copy.deepcopy(a)

# ⚠️ 循环中删除
nums = [1, 2, 3, 4, 5]
for n in nums:
    if n % 2 == 0:
        nums.remove(n)        # 索引偏移,漏删!

# ✅ 正确做法:倒序遍历或列表推导
nums[:] = [n for n in nums if n % 2 != 0]

2️⃣ dict --- 哈希表

底层实现

Python 3.7+ 的 dict 是 有序哈希表(Compact Hash Table)

  • 键通过 hash() 计算哈希值,取模定位
  • 冲突解决:开放地址法(Open Addressing)
  • 插入顺序被保留(3.7+ 语言特性,之前也有 CPython 实现)
  • 3.6+ 引入 PyDictKeysObject,分离键索引和值数组,内存减少 20-30%

高级操作

python 复制代码
# setdefault --- 一行搞定「取/设默认值」
data = {}
users = data.setdefault('users', [])
users.append('张三')
# data → {'users': ['张三']}

# defaultdict --- 自动工厂
from collections import defaultdict
groups = defaultdict(list)
groups['dev'].append('张三')   # 自动创建空列表
groups['dev'].append('李四')

# dict 推导 + 过滤
squares = {x: x**2 for x in range(10) if x > 5}
# {6: 36, 7: 49, 8: 64, 9: 81}

# 「|」合并操作符(3.9+)
config = {'host': 'localhost', 'port': 8080}
override = {'port': 9000, 'debug': True}
merged = config | override                 # {'host': 'localhost', 'port': 9000, 'debug': True}

# 解包合并
merged = {**config, **override}

# get() 的巧妙用法
user = users.get('admin') or 'default_admin'  # 取到 None 时给默认

# 缺失键触发
d = {}
d.setdefault('count', 0)
d['count'] += 1                     # 安全,无需先判断

避坑指南

python 复制代码
# ⚠️ 不可哈希的键
# d = {[1, 2]: 'value'}  ← TypeError: unhashable type: 'list'
# 解决方案:用 tuple 或 frozenset

# ⚠️ 循环中修改
d = {'a': 1, 'b': 2, 'c': 3}
# for k in d:       # RuntimeError: dictionary changed size during iteration
#     if k == 'b':
#         del d[k]

# ✅ 安全删除方式
for k in list(d.keys()):   # 快照迭代
    if k == 'b':
        del d[k]

# ⚠️ 自定义对象做键
class User:
    def __init__(self, name):
        self.name = name
    # 必须实现 __hash__ 和 __eq__

u1, u2 = User('张三'), User('张三')
d = {u1: 'first'}
print(d.get(u2))  # None --- 两个对象不相等

3️⃣ set --- 无序集合

底层实现

set 的底层也是哈希表,和 dict 的键实现一样:

  • 元素必须可哈希
  • O(1),增 O(1) 摊还
  • 不保证顺序(但 CPython 实现中插入顺序有规律,不要依赖!)

高级操作

python 复制代码
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

a | b   # 并集 → {1, 2, 3, 4, 5, 6}
a & b   # 交集 → {3, 4}
a - b   # 差集 → {1, 2}
a ^ b   # 对称差 → {1, 2, 5, 6}

# 超集/子集判断
{1, 2}.issubset({1, 2, 3})      # True
{1, 2, 3}.issuperset({1, 2})    # True
{1, 2}.isdisjoint({3, 4})       # True(无交集)

# 去重神器
names = ['张三', '李四', '张三', '王五']
unique = list(set(names))        # ['张三', '李四', '王五']
# ⚠️ 但会丢失顺序!要保留顺序用:

# 保留顺序的去重
seen = set()
ordered = []
for name in names:
    if name not in seen:
        seen.add(name)
        ordered.append(name)

# frozenset --- 不可变集合(可以用作 dict 的键)
frozen = frozenset([1, 2, 3])
d = {frozen: 'hashable'}

避坑指南

python 复制代码
# ⚠️ 空集合不是 {}
empty_set = {}          # 这是 dict!
empty_set = set()       # ✅ 正确的空集合

# ⚠️ 集合推导是 {}
squares = {x**2 for x in range(5)}  # {0, 1, 4, 9, 16} ← set

4️⃣ tuple --- 不可变序列

底层实现

  • 底层也是数组,但不可变
  • 因为不可变,可以哈希(元素也是可哈希的)
  • 创建后不能增删改,遍历更快,内存更省

高级操作

python 复制代码
# 命名元组 --- 轻量级数据类
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
p.x        # → 10
p.y        # → 20
x, y = p   # 解包

# 带类型注解的命名元组(3.6+)
from typing import NamedTuple

class Employee(NamedTuple):
    name: str
    age: int
    department: str

e = Employee('张三', 28, '技术部')
e.name     # '张三'

# 单元素元组的逗号
single = (42,)         # ✅ tuple
not_tuple = (42)       # 这只是 int!

# `*` 元素解包
first, *middle, last = (1, 2, 3, 4, 5)
# first=1, middle=[2,3,4], last=5

避坑指南

python 复制代码
# ⚠️ 元组的不可变是浅层的
t = ([1, 2], [3, 4])
t[0].append(999)    # 可以!t 变成 ([1,2,999], [3,4])
# t[0] = []         # ❌ 不可以!会报 TypeError

# ⚠️ 命名元组是元组的子类,但不是数据类的替代
# 如果字段多且需要校验,用 @dataclass

🏆 性能对比速查

操作 list dict set tuple
索引/查找 O(1) O(1) --- O(1)
包含检查 O(n) O(1) O(1) O(n)
尾部插入 O(1)‡ O(1)‡ O(1)‡
任意插入 O(n) O(1) ---
删除 O(n) O(1) O(1)
内存 最少
可哈希 仅键

‡ 摊还时间复杂度


📌 要点总结

容器 核心特性 最适合的场景
list 有序、可重复、可变 按索引访问、顺序遍历、栈/队列
dict 键值对、有序(3.7+)、键唯一 映射/缓存/配置/分组
set 无序、元素唯一、数学运算 去重/交集/差集/成员判断
tuple 有序、不可变、可哈希 函数多返回值/字典键/不可变数据
  • 判断成员存在set / dict keys 远快于 list
  • 需要顺序:用 list 或 dict(3.7+ 有序)
  • 需要不可变:用 tuple,还能做 dict 的键
  • 优先用推导式:可读性更高、通常更快
  • 不要依赖集合顺序:不同 Python 实现可能不同
相关推荐
从零开始学习人工智能5 小时前
【踩坑实录】WSL2 解决 onnxruntime\-gpu ImportError: libcudart\.so\.13 无 CUDA13 运行库问题
python
东风破_6 小时前
ESLint 是什么?为什么你的项目需要它?
前端·后端·代码规范
嘻哈∠※6 小时前
0061基于 SpringBoot 的投稿与稿件处理系统设计与实现
java·spring boot·后端
卷无止境6 小时前
在 awesome-fastapi 里,哪些库值得一看?
后端·python
zhanghaha13146 小时前
Python进阶教程:6_JSON 数据解析 —— 新手完全指南
开发语言·python·json
Python私教7 小时前
API 输出模型怎么设计:从 model_dump() 到显式展示层
python·fastapi
Python私教7 小时前
本地 AI 工具服务该绑定 127.0.0.1 还是 0.0.0.0?
python·fastapi
卷无止境7 小时前
FastAPI 的Admin面板生态
后端·python
ctlover8 小时前
Streamlit 框架
python
ι:8 小时前
MATLAB 与 Python 搭建无人机地面站:优势、劣势与选型逻辑
python·matlab·无人机