系列文章

Python 从入门到精通

第 16 / 36 篇

从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。

  1. 01
    Python 怎么装、怎么用
  2. 02
    变量、数字和字符串
  3. 03
    列表:把一组数据放在一起
  4. 04
    元组、集合和字典
  5. 05
    条件判断:让程序做选择
  6. 06
    循环:重复的事交给程序
  7. 07
    函数:把代码整理成可复用的块
  8. 08
    模块与包:拆分你的程序
  9. 09
    输入、输出与字符串格式化
  10. 10
    文件读写:保存程序的数据
  11. 11
    异常处理:程序出错时怎么办
  12. 12
    基础阶段练习:命令行记账本
  13. 13
    类和对象:面向对象入门
  14. 14
    继承、组合与特殊方法
  15. 15
    迭代器与生成器
  16. 16
    列表推导式与生成器表达式正在阅读
  17. 17
    装饰器:给函数增加能力
  18. 18
    上下文管理器与 with
  19. 19
    类型标注与 dataclass
  20. 20
    正则表达式:从文本中找规律
  21. 21
    日期、时间与时区
  22. 22
    日志与调试
  23. 23
    虚拟环境与依赖管理
  24. 24
    测试:让修改不再提心吊胆
  25. 25
    网络请求:用 Python 调用 API
  26. 26
    网页解析与合规采集
  27. 27
    操作 Excel、CSV 与批量文件
  28. 28
    SQLite:给程序加一个数据库
  29. 29
    数据分析入门:NumPy 与 Pandas
  30. 30
    画图:把数据变得直观
  31. 31
    Flask 入门:做一个小网站
  32. 32
    异步编程:同时处理多项任务
  33. 33
    线程、进程与并发选择
  34. 34
    性能分析与优化
  35. 35
    项目结构、配置与发布
  36. 36
    综合项目:从需求到上线

查看整个系列 →

写 Python 的人都知道一个说法:能用一行推导式解决的,别写三行循环。推导式(comprehension)是 Python 最优雅的语法之一,这一篇把列表、字典、集合三种推导式一次讲透,外加生成器表达式。

列表推导式:循环的压缩版

最常见的需求:把一个列表变换成另一个列表。传统写法 vs 推导式:

nums = [1, 2, 3, 4, 5]

# 传统写法
squares = []
for n in nums:
    squares.append(n * n)

# 推导式(一行搞定)
squares = [n * n for n in nums]
print(squares)   # [1, 4, 9, 16, 25]

结构拆解:[表达式 for 变量 in 可迭代对象]——把”要什么”写在前面,把”从哪来”写在后面。读法:对 nums 里每个 n,产出 n*n。

带条件的推导式

后面加 if 过滤:

nums = [1, 2, 3, 4, 5, 6, 7, 8]
# 只要偶数,再平方
evens_squared = [n * n for n in nums if n % 2 == 0]
print(evens_squared)   # [4, 16, 36, 64]

# 实战:从一堆字符串里挑出长度大于 2 的
words = ["南山小站", "开张了", "好", "python"]
long_words = [w for w in words if len(w) > 2]
print(long_words)      # ['南山小站', '开张了', 'python']

else 也要?用三元表达式

想”符合条件的变一个样,不符合的变另一个样”,把三元表达式放前面:

nums = [1, 2, 3, 4, 5]
# 偶数×10,奇数不变
result = [n * 10 if n % 2 == 0 else n for n in nums]
print(result)   # [1, 20, 3, 40, 5]

注意顺序:[值 if 条件 else 其他值 for ...]——if/else 在表达式位置(前面),单独的 if 过滤在后面。位置别搞混。

嵌套循环的推导式

两层循环也能压成一行,但可读性要注意:

# 两个列表的笛卡尔积
colors = ["红", "蓝"]
sizes = ["S", "M"]
combos = [f"{c}{s}" for c in colors for s in sizes]
print(combos)   # ['红S', '红M', '蓝S', '蓝M']

# 等价的三层写法
combos = []
for c in colors:
    for s in sizes:
        combos.append(f"{c}{s}")

写嵌套推导式时心里默念:for 的顺序和普通循环一样(外层先内层后)。超过两层就别用推导式了,可读性崩坏。

字典推导式

花括号 + 键值对:

# 数字 → 平方的字典
squares = {n: n * n for n in range(1, 6)}
print(squares)   # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# 实战:统计字符串里每个字符出现次数
text = "hello"
counts = {ch: text.count(ch) for ch in set(text)}
print(counts)    # {'h': 1, 'e': 1, 'l': 2, 'o': 1}

# 反转字典(键值互换)
d = {"a": 1, "b": 2}
reversed_d = {v: k for k, v in d.items()}
print(reversed_d)   # {1: 'a', 2: 'b'}

集合推导式

同样的语法,花括号但没有冒号,自动去重:

nums = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {n * n for n in nums}
print(unique_squares)   # {1, 4, 9, 16}(自动去重)

# 实战:提取所有句子里的唯一单词
sentences = ["hello world", "hello python"]
words = {w for s in sentences for w in s.split()}
print(words)   # {'hello', 'world', 'python'}

生成器表达式:圆括号版

把方括号换成圆括号,就是生成器表达式——不立即计算,遍历时才算

nums = range(100_000_000)          # 1 亿个数
gen = (n * n for n in nums)        # 几乎不占内存
print(sum(gen))                    # 边算边加

# 对比:列表推导式会先造一个 1 亿元素的列表
# lst = [n * n for n in nums]      # 内存爆炸,别试!

判断标准:只要遍历一次就用生成器表达式(sum/max/any/all 都是只遍历一次),要反复用、要下标才用列表。

什么时候别用推导式

推导式不是万能的,三条红线:

  • 逻辑复杂时:超过两个 for 或条件很绕,写普通循环,可读性优先
  • 要调试打印时:推导式里没法插 print,循环可以
  • 有副作用时:推导式应该”纯计算”,别在里面做文件写入、网络请求

好代码的第一标准是别人(三个月后的你)能看懂,其次才是短。

新手坑

坑 1:if 位置放错。[n for n in nums if n > 2 else 0] 语法错误——带 else 的三元表达式必须放最前面。

坑 2:变量遮蔽。推导式里的循环变量会泄漏到外层作用域(Python 2 遗留),同名变量小心被覆盖。Python 3 里列表推导式不泄漏,但别依赖。

坑 3:为了短而短。一行 200 字符的推导式没人看得懂,拆开写。

小结

  • 列表推导式 [expr for x in it],可加 if 过滤、三元表达式变形
  • 字典推导式 {k: v for ...},集合推导式 {x for ...}(自动去重)
  • 生成器表达式用圆括号,只遍历一次时用,省内存
  • 嵌套 for 顺序和普通循环一致;超过两层就别硬写
  • 可读性永远优先,复杂逻辑用普通循环

练习

  1. 用推导式把 ["1", "2", "3"] 转成整数列表 [1, 2, 3](提示:int)
  2. 用推导式从 range(1, 101) 里筛出能被 3 整除但不能被 5 整除的数
  3. 思考:[x for x in range(10)](x for x in range(10)) 有什么区别?什么时候必须用后者?