这一章讲 Python 的「内功」:迭代协议(for 循环的真相)、生成器(惰性求值,内存的救星)、高阶函数(函数式三板斧)。它们是「Pythonic」的分水岭:会用的人代码又短又省内存,不会的人手写一堆临时列表。数据结构课第 2 章的「链表按需访问」、计组课第 7 章(存储层次)的「按需加载」,在这一章找到了 Python 语法级对应。
for 循环不是「数下标」,而是一次次向对象要下一个元素。协议两层:
__iter__(返回一个迭代器)。列表、字符串、字典、文件、range 全是;__next__(返回下一个,没有则抛 StopIteration)+ __iter__。it = iter([10, 20]) # 手动走一遍 for 的内部过程
next(it) # 10
next(it) # 20
next(it) # StopIteration → for 在这里优雅收尾
for x in obj 的机器级真相 = iter(obj) 拿迭代器 + 反复 next + StopIteration 收尾。文件对象就是迭代器(第 5 章 for line in f 的原理:逐行惰性读,内存恒定)。理解协议后的红利:任何自定义类实现 __iter__/__next__ 就能被 for 遍历(第 6 章鸭子类型:实现协议即获得能力)。
生成器(generator)是「会暂停的函数」:yield 处交出一个值并冻结现场,下次调用从冻结处继续:
def countdown(n):
while n > 0:
yield n # 交出一个值,暂停在这
n -= 1
for x in countdown(3): # 3, 2, 1
print(x)
g = countdown(10**9) # 瞬间创建!一个十亿数的「流」
next(g), next(g) # 10, 9 —— 不占十亿份内存
对比列表:列表是「全造好放着」,生成器是「要一个造一个」(惰性求值)。两个杀手级场景:大文件处理(逐行生成,内存恒定,第 5 章)、无限序列(斐波那契生成器,永不结束也不爆内存):
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
from itertools import islice
list(islice(fib(), 8)) # [0, 1, 1, 2, 3, 5, 8, 13](取前 8 个)
生成器表达式(第 3 章的括号版推导式)是它的语法糖:sum(n*n for n in range(10**8)) 全程 O(1) 内存(换成列表推导式直接内存爆炸)。「能用生成器就不造列表」是处理大数据的第一戒律。
生成器的真正威力在串联(Unix 管道思想,第 13 章 Java 网络课的流):
def read_lines(path):
with open(path, encoding="utf-8") as f:
for line in f:
yield line.strip()
def filter_errors(lines):
for line in lines:
if "ERROR" in line:
yield line
def truncate(lines, n=50):
for line in lines:
yield line[:n]
# 三段管道:每个生成器都不知道上家是谁,流式流过
for line in truncate(filter_errors(read_lines("big.log"))):
print(line)
数据像水流过三节管道,内存里同时只存在一行;中间任何一段换成不同的生成器(不同数据源、不同过滤),管道照常工作。这是 Unix 哲学(小工具组合)在语言级的实现,也是第 10 章(处理几个 GB 的 CSV 不爆内存)的技术底座。
Python 的函数式不全纯,但三件套值得会用:
nums = [3, -1, 4, -1, 5]
list(map(abs, nums)) # [3, 1, 4, 1, 5](对每个元素施函数)
list(filter(lambda x: x > 0, nums)) # [3, 4, 5](过滤)
sorted(nums, key=abs) # [-1, -1, 3, 4, 5](按绝对值排序!)
sorted(users, key=lambda u: u.age, reverse=True) # 按字段倒序
key= 是 Python 排序的灵魂(不写比较函数,写「取排序依据」的函数;对照 Java 课第 11 章的 Comparator 和计组课第 11 章 qsort 回调,同一思想三代语法)。lambda 是匿名小函数(单表达式,复杂逻辑用 def);functools.reduce(累积累积)、functools.partial(固定部分参数)是进阶工具。风格建议:Python 社区更爱推导式(8.2)多于 map/filter 链,两者都要能读。
from itertools import islice, chain, groupby, count, product
islice(fib(), 5, 10) # 惰性切片(无限序列的窗口)
chain([1,2], [3,4]) # 串接多个可迭代
count(start=10) # 无限计数器
product("AB", repeat=2) # 笛卡尔积:(A,A) (A,B) (B,A) (B,B)(离散课第 1 章)
sorted(words, key=len, reverse=True)[:3] # 最长的三个词
max(scores, key=lambda s: s.value) # 最大者(不是最大值,是拿最大值的元素!)
any(x > 0 for x in nums), all(x > 0 for x in nums) # 存在/全称(惰性短路)
max(key=)、any/all + 生成器表达式是高频惯用法:any 找到即停(短路与第 2 章同款),全称判断直接对译离散数学的 ∃/∀(离散课第 3 章)。
g = (x for x in ...); sum(g); sum(g) 第二次得 0(耗尽了);要复用转列表 list(g)(付出内存)。return x 等于抛 StopIteration(x) 细节坑;终止用 return 或 raise。print(map(f, lst)) 只打印对象地址;要 list() 落地(或 for 消费)。def read_numbers(path) 逐行产出文件里的整数(跳过空行与非数字行);用 sum(read_numbers("data.txt")) 统计总和;把文件换成 100 万行验证内存恒定(任务管理器看)。primes()(试除法),用 islice 取前 20 个素数;体会「无限流」在列表世界里不可能、在生成器里自然。