Python: 容器对比与全能推导式
前几课我们把列表、元组、字典、集合一个个学完了。这一课来做个横向对比——它们到底有什么不同?什么时候该用哪个?同时把推导式这个 Python 神器彻底讲透。
1. 四大容器对比
| 特性 | 列表 (list) |
元组 (tuple) |
字典 (dict) |
集合 (set) |
|---|---|---|---|---|
| 符号 | [] |
() |
{key: value} |
{} 或 set() |
| 有序 | ✅ 有顺序 | ✅ 有顺序 | ✅ 插入顺序(3.7+) | ❌ 无序 |
| 可变 | ✅ | ❌ | ✅ | ✅ |
| 可重复 | ✅ | ✅ | 键唯一、值可重复 | ❌ 元素唯一 |
| 访问方式 | 索引 [0] |
索引 [0] |
键 ["key"] |
不支持索引 |
| 查找速度 | O(n) 慢 | O(n) 慢 | O(1) 极快 | O(1) 极快 |
| 典型用途 | 有序数据、动态集合 | 固定数据、函数返回值 | 映射关系、缓存 | 去重、集合运算 |
💡 关键选型原则: 1)需要按位置取 → 列表/元组;2)需要按名字取 → 字典;3)需要去重或数学运算 → 集合;4)数据不该变 → 元组。
▶ 示例:容器选型对比(难度⭐⭐)
PYTHON
# 同一个场景用不同容器实现
# 场景:存储学生姓名
# 用列表——有顺序,方便增删
students_list = ["Alice", "Bob", "Charlie"]
students_list.append("赵六")
print(f"列表:{students_list[0]}") # Alice
# 用元组——固定不变(比如班级花名册)
students_tuple = ("Alice", "Bob", "Charlie")
# students_tuple[0] = "xxx" # 报错!元组不可修改
print(f"元组:{students_tuple[0]}") # Alice
# 用字典——按学号快速查找
students_dict = {
"001": "Alice",
"002": "Bob",
"003": "Charlie",
}
print(f"字典:{students_dict['002']}") # Bob——O(1) 速度
# 用集合——去重(比如签到名单)
attendance = {"Alice", "Bob", "Charlie", "Alice"}
print(f"集合:{attendance}") # {'Bob', 'Charlie', 'Alice'}——重复的Alice被去重
输出:
TEXT
📖 仅展示
# 执行成功
2. 列表推导式强化
第 11 课学了基础推导式,这里深入更多技巧。
(1) 多条件过滤
PYTHON
numbers = range(1, 31)
# 能被 3 整除且能被 5 整除
filtered = [n for n in numbers if n % 3 == 0 and n % 5 == 0]
print(filtered) # [15, 30]
# 能被 3 整除或能被 5 整除
filtered = [n for n in numbers if n % 3 == 0 or n % 5 == 0]
print(filtered) # [3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30]
(2) 嵌套循环推导式
PYTHON
# 生成坐标对——相当于双重 for 循环
pairs = [(x, y) for x in range(3) for y in range(3)]
print(pairs)
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
# 带条件的嵌套——找出所有两数之和为偶数的组合
pairs = [(x, y) for x in range(3) for y in range(3) if (x + y) % 2 == 0]
print(pairs)
# [(0, 0), (0, 2), (1, 1), (2, 0), (2, 2)]
(3) if-else 在推导式中
PYTHON
# if-else 在推导式中的写法——注意位置不同
numbers = [1, 2, 3, 4, 5, 6]
# 只有 if(过滤)——写在后面
evens = [n for n in numbers if n % 2 == 0]
# if-else(变换)——写在前面
result = ["偶数" if n % 2 == 0 else "奇数" for n in numbers]
print(result) # ['奇数', '偶数', '奇数', '偶数', '奇数', '偶数']
▶ 示例:数据转换流水线(难度⭐⭐⭐)
PYTHON
# 一行代码完成:筛选→转换→格式化
raw = [" apple ", "BANANA", "", " CHERRY", None, " date "]
# 过滤掉 None 和空字符串 → 去掉空白 → 首字母大写
cleaned = [item.strip().capitalize() for item in raw if item and item.strip()]
print(cleaned) # ['Apple', 'Banana', 'Cherry', 'Date']
运行结果:
TEXT
📖 仅展示
['Apple', 'Banana', 'Cherry', 'Date']
3. 字典推导式
字典也可以推导,语法和列表推导式类似,只是用 {} 并指定键值对:
PYTHON
# 基本字典推导式
squares = {x: x ** 2 for x in range(5)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 带条件
even_squares = {x: x ** 2 for x in range(10) if x % 2 == 0}
print(even_squares) # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
# 反转字典的键和值
original = {"a": 1, "b": 2, "c": 3}
reversed_dict = {value: key for key, value in original.items()}
print(reversed_dict) # {1: 'a', 2: 'b', 3: 'c'}
(1) 实用场景:列表转字典
PYTHON
# 两个列表合并成字典
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
result = {names[i]: scores[i] for i in range(len(names))}
print(result) # {'Alice': 85, 'Bob': 92, 'Charlie': 78}
# 用 zip() 更优雅
result = {name: score for name, score in zip(names, scores)}
print(result) # {'Alice': 85, 'Bob': 92, 'Charlie': 78}
4. 集合推导式
集合推导式用 {} 但只有表达式没有冒号——和字典的区别在于有没有冒号:
PYTHON
# 集合推导式——自动去重
numbers = [1, 2, 2, 3, 3, 3, 4, 5, 5]
unique_squares = {x ** 2 for x in numbers}
print(unique_squares) # {1, 4, 9, 16, 25}
# 带条件
even_set = {x for x in range(20) if x % 2 == 0}
print(even_set) # {0, 2, 4, 6, 8, 10, 12, 14, 16, 18}
💡 三者的区别:
[x for x in ...] 是列表 → 有序、可重复。{x: y for x in ...} 是字典 → 有冒号。{x for x in ...} 是集合 → 无冒号、无序、去重。
▶ 示例:词语分析(难度⭐⭐⭐)
PYTHON
# 分析一篇文章的词汇
article = """
Python is great. Python is powerful!
I love Python. JavaScript is also great.
But Python is my favorite.
"""
# 提取所有单词
words = article.lower().split()
# 用集合推导式去重
unique_words = {word.strip(".!,") for word in words}
print(f"总词数:{len(words)}")
print(f"不重复词数:{len(unique_words)}")
print(f"所有不重复词:{sorted(unique_words)}")
运行结果:
TEXT
📖 仅展示
总词数:18
不重复词数:10
所有不重复词:['also', 'but', 'favorite', 'great', 'is', 'javascript', 'love', 'my', 'python', 'i']
5. 常见应用场景
- 数据清洗流水线:列表推导式一行完成过滤空值、去空白、转换格式。
- 字典映射反转:字典推导式交换键和值,常用于反向查找。
- 去重统计:集合推导式快速统计不重复元素。
- 条件分组:结合 if-else 推导式把数据分成两组(如"及格/不及格")。
- 坐标生成:嵌套推导式生成棋盘坐标、像素点等二维数据。
❓ 常见问题
Q 列表推导式和生成器表达式有什么区别?
A 列表推导式
[x for x in range(10)] 一次性生成全部数据存在内存中。生成器表达式 (x for x in range(10)) 是惰性的——用到一个才生成一个,省内存。数据量大时(几万条以上)用生成器。Q 推导式嵌套超过两层怎么办?
A 超过两层的推导式可读性急剧下降。
[x for xs in matrix for ys in xs for x in ys] 这种代码几乎没人能一眼看懂。如果逻辑需要三层以上,拆成普通 for 循环或者先写几个辅助函数。Q 怎么判断一个数据该用哪个容器?
A 三步法:① 是否需要唯一键?→ 是则字典。② 是否需要去重或集合运算?→ 是则集合。③ 数据需要修改吗?→ 是则列表,否则元组。大多数场景下,列表和字典覆盖了 90% 的需求。
📖 小节
- 列表有序可变、元组有序不可变、字典键值对查找快、集合适用于去重和数学运算
- 列表推导式:
[表达式 for 变量 in 可迭代对象 if 条件] - 字典推导式:
{键: 值 for 变量 in 可迭代对象 if 条件} - 集合推导式:
{表达式 for 变量 in 可迭代对象 if 条件} - 推导式中 if 放后面是过滤,if-else 放前面是变换
- 嵌套推导式可以实现二维数据生成,但超过两层建议用循环
📝 作业
-
基础题(难度⭐):用列表推导式生成 10 到 50 之间所有能被 7 整除或包含数字 7 的数字。提示:用
or连接两个条件。 -
进阶题(难度⭐⭐):给定两个列表
keys = ["name", "age", "city"]和values = ["Alice", 25, "北京"],用字典推导式把它们合并成一个字典。提示:用zip(keys, values)配对。 -
挑战题(难度⭐⭐⭐):写一个"数据统计器"。给定一个包含多个句子的字符串,统计并输出:
- 总单词数
- 不重复单词数
- 出现频率最高的 3 个单词
- 提示:用
split()分词、字典计数、sorted(dict.items(), key=lambda x: x[1], reverse=True)[:3]取前三。