一、先从一个简单问题开始什么叫“迭代”迭代就是“挨个儿取东西”。比如你有一排抽屉你从左到右一个个打开看这个过程就是迭代。Python里最常见的迭代就是for ... in ...for i in [1, 2, 3]: print(i)这个列表[1, 2, 3]就是一个可迭代对象Iterable因为它可以被for循环一个一个地取出元素。如何判断一个对象是否可迭代from collections.abc import Iterable print(isinstance([1,2,3], Iterable)) # True print(isinstance(abc, Iterable)) # True print(isinstance(123, Iterable)) # False一切可以被for循环遍历的东西基本都是可迭代对象列表、字符串、字典、文件、生成器等。二、揭秘for循环背后的男人迭代器2.1 什么又是迭代器Iterator迭代器是一个“记住了取到哪一步”的对象。它给你一个next()方法每次调用就返回下一个值直到没有元素时抛出StopIteration异常。你可以把迭代器想象成一个弹夹每按一下扳机next()就出一颗子弹打完了就报错。2.2 手动体验迭代器my_list [10, 20, 30] it iter(my_list) # 通过 iter() 把可迭代对象变成迭代器 print(next(it)) # 10 print(next(it)) # 20 print(next(it)) # 30 print(next(it)) # 抛出 StopIteration为什么需要迭代器因为不是所有可迭代对象都能一次加载到内存比如一个100GB的日志文件。用迭代器可以惰性求值——需要的时候才生成下一个值省内存。2.3 自己动手实现一个迭代器类要实现一个迭代器必须实现两个方法__iter__()返回自身或其它迭代器对象__next__()返回下一个值无值时抛StopIteration下面我们写一个斐波那契数列迭代器只存前两个数永远不占用大量内存class Fibonacci: def __init__(self, max_count): self.max max_count self.a 0 self.b 1 self.count 0 def __iter__(self): return self # 迭代器返回自身 def __next__(self): if self.count self.max: raise StopIteration self.count 1 value self.a self.a, self.b self.b, self.a self.b return value # 使用 fib Fibonacci(10) for num in fib: print(num, end ) # 0 1 1 2 3 5 8 13 21 34是不是觉得手写一个迭代器挺麻烦别急接下来更简单的生成器就登场了。三、生成器用yield让函数记住暂停点如果一个函数里出现了yield关键字它就不再是普通函数而是一个生成器函数。调用它不会立即执行而是返回一个生成器对象也属于迭代器。3.1 第一个生成器无限计数器def count_up_to(n): i 0 while i n: yield i # 每次返回 i并暂停在这里 i 1 gen count_up_to(3) print(next(gen)) # 0 print(next(gen)) # 1 print(next(gen)) # 2执行过程第一次调用next(gen)→ 从函数开头运行遇到yield i返回0并冻结当前状态。第二次调用next(gen)→ 从上次暂停的地方继续i还是1然后yield返回1。直到循环结束函数自然返回时抛出StopIteration。3.2 生成器比列表省多少内存假设你想产生1亿个数# 列表方式 —— 内存爆炸 nums [x for x in range(100_000_000)] # 约800MB # 生成器方式 —— 几乎不占内存 def my_range(n): i 0 while i n: yield i i 1 gen my_range(100_000_000) # 只存一个整数 i用生成器不管要产生多少个数内存占用都是O(1)。3.3 生成器表达式简洁版生成器类似于列表推导式但把[]换成()# 列表推导式立刻生成全部数据 squares_list [x**2 for x in range(10)] # 生成器表达式惰性生成 squares_gen (x**2 for x in range(10)) print(type(squares_gen)) # class generator print(next(squares_gen)) # 0 print(next(squares_gen)) # 1何时用生成器表达式数据量大且只需要遍历一次时用生成器表达式代替列表推导式能大幅降低内存。四、进阶技巧yield from、send()、throw()、close()4.1yield from—— 委托给另一个生成器当你在一个生成器里想要产生另一个可迭代对象的所有值时不要写循环直接用yield fromdef chain(*iterables): for it in iterables: yield from it # 相当于 for x in it: yield x c chain([1,2,3], abc) print(list(c)) # [1, 2, 3, a, b, c]yield from还能自动传递send()和throw()在写复杂协程时特别有用。4.2send()—— 给生成器内部“喂”数据普通next()只能从生成器取数据send()不仅能取还能传进去一个值。def echo(): while True: received yield # yield 可以接收外部发送的值 print(f收到了: {received}) e echo() next(e) # 必须先启动到 yield 处 e.send(Hello) # 打印 收到了: Hello e.send(World) # 打印 收到了: World注意第一次必须先next(e)或e.send(None)让生成器运行到第一个yield位置。4.3throw()—— 在生成器内部抛出异常可以强制在生成器暂停的地方抛出一个异常def my_gen(): try: yield 1 yield 2 except ValueError: print(捕获到 ValueError) g my_gen() print(next(g)) # 1 g.throw(ValueError) # 打印 捕获到 ValueError4.4close()—— 提前终止生成器def infinite(): while True: yield 1 g infinite() print(next(g)) # 1 print(next(g)) # 1 g.close() print(next(g)) # 抛出 StopIteration不会再生成五、实战小例子用生成器读取超大日志文件假设你有一个10GB的日志文件app.log需要找出所有包含ERROR的行。用生成器可以一行一行读内存无忧def read_large_file(file_path): with open(file_path) as f: for line in f: yield line # 每次只返回一行 def filter_errors(lines): for line in lines: if ERROR in line: yield line # 链式调用全程惰性求值 errors filter_errors(read_large_file(app.log)) for err in errors: print(err.strip())你也可以用生成器表达式一步到位with open(app.log) as f: errors (line for line in f if ERROR in line) for err in errors: print(err.strip())六、总结记住这几点就够了概念一句话解释手写示例可迭代对象能用for循环的如list,str,file[1,2,3]迭代器实现了__next__()的对象可被next()调用iter([1,2,3])生成器函数含有yield的函数调用返回生成器对象def gen(): yield 1生成器表达式圆括号包裹的推导式(x*2 for x in range(5))yield from委托给另一个生成器或可迭代对象yield from abcsend()给生成器内部传递值gen.send(42)什么时候优先用生成器需要处理大数据流文件、数据库结果集、网络流需要无限序列如实时监控、轮询想要实现“管道式”的数据处理避免中间列表