68_Python生成器与迭代器

📅 2026/7/1 18:37:44
68_Python生成器与迭代器
Python生成器与迭代器惰性求值与内存友好的编程艺术文章目录Python生成器与迭代器惰性求值与内存友好的编程艺术前言一、迭代器基础1.1 可迭代对象 vs 迭代器1.2 for 循环的幕后原理1.3 自定义迭代器二、生成器入门2.1 使用 yield 创建生成器2.2 yield 的执行机制2.3 yield from委托子生成器三、生成器表达式四、itertools生成器工具箱五、生成器的高级用法5.1 双向通信send() 方法5.2 数据管道管道串联执行七、迭代器协议检查与常见坑总结✅ 亮点总结适用场景扩展方向前言处理大规模数据时一次性将所有数据加载到内存往往不现实——你也许需要逐行处理一个10GB的日志文件或者生成一个理论上是无限的数列。Python的**迭代器Iterator和生成器Generator**提供了优雅的解决方案按需生成数据而非一次性存储所有数据。这种惰性求值策略是Python中内存管理的利器。迭代器与生成器在面试中的地位它们是Python高级特性的敲门砖。理解迭代器协议__iter__/__next__是理解for循环底层原理的前提掌握生成器则是理解协程和异步编程的基础。面试官常问生成器和列表的区别、如何实现一个自定义迭代器等问题本文会逐一给出清晰答案。一、迭代器基础1.1 可迭代对象 vs 迭代器这是学习迭代器的第一道认知门槛。很多初学者混淆了可迭代对象和迭代器两个概念。核心区别可迭代对象Iterable是任何可以被for...in遍历的对象它实现了__iter__方法迭代器Iterator则进一步实现了__next__方法可以惰性地逐个产出值。比喻可迭代对象像一个书籍目录——你知道里面有什么迭代器像一个书签——你知道当前位置并能翻到下一页。列表是可迭代对象但不是迭代器iter(list)返回的才是迭代器。# 可迭代对象Iterable可以被for...in遍历的对象# 迭代器Iterator实现了__next__方法的对象fromcollections.abcimportIterable,Iterator my_list[1,2,3]print(isinstance(my_list,Iterable))# Trueprint(isinstance(my_list,Iterator))# False - 列表是可迭代对象但不是迭代器# 使用 iter() 将可迭代对象转为迭代器list_iteriter(my_list)print(isinstance(list_iter,Iterator))# True# 手动迭代print(next(list_iter))# 1print(next(list_iter))# 2print(next(list_iter))# 3# print(next(list_iter)) # 触发 StopIteration 异常1.2for循环的幕后原理# for 循环本质上是这样工作的numbers[10,20,30]# 等价于以下的 while 循环iteratoriter(numbers)whileTrue:try:itemnext(iterator)print(item)exceptStopIteration:break1.3 自定义迭代器classCountdown:倒计时迭代器def__init__(self,start):self.currentstartdef__iter__(self):returnselfdef__next__(self):ifself.current0:raiseStopIteration valueself.current self.current-1returnvalue# 使用自定义迭代器fornuminCountdown(5):print(num,end )# 5 4 3 2 1 0print()# 也可以手动迭代cdCountdown(3)print(next(cd))# 3print(next(cd))# 2二、生成器入门生成器是Python中最强大的特性之一。它的本质是一个可以暂停和恢复的函数——每次yield产生一个值后函数的状态局部变量、指令指针等被完整保存下来等下次next()调用时再恢复。这种保存现场、下次恢复的机制也是Python协程coroutine和 asyncio 的实现基础。2.1 使用yield创建生成器生成器是创建迭代器最简单的方式——只需要在函数中使用yield关键字。比起手动实现__iter__和__next__方法生成器函数大大减少了样板代码同时让逻辑表达更加直观。defcountdown_gen(start):生成器版本的倒计时currentstartwhilecurrent0:yieldcurrent current-1# 调用生成器函数返回一个生成器对象gencountdown_gen(5)print(type(gen))# class generator# 迭代生成器fornumingen:print(num,end )# 5 4 3 2 1 02.2yield的执行机制defdemo_generator():print(生成器开始执行)yield1print(恢复执行yield 第2个值)yield2print(恢复执行yield 第3个值)yield3print(生成器执行完毕)gendemo_generator()print(调用 next(gen) 第1次:)print(返回值:,next(gen))print()print(调用 next(gen) 第2次:)print(返回值:,next(gen))print()print(调用 next(gen) 第3次:)print(返回值:,next(gen))输出调用 next(gen) 第1次: 生成器开始执行 返回值: 1 调用 next(gen) 第2次: 恢复执行yield 第2个值 返回值: 2 调用 next(gen) 第3次: 恢复执行yield 第3个值 返回值: 3yield让函数在交出值的地方暂停下次next()调用时从暂停处继续执行——这就是生成器的惰性求值本质。2.3yield from委托子生成器defsub_generator_a():yieldA1yieldA2yieldA3defsub_generator_b():yieldB1yieldB2defmain_generator():yieldStart# yield from 简化子生成器的迭代yieldfromsub_generator_a()yieldMiddleyieldfromsub_generator_b()yieldEndforiteminmain_generator():print(item,end )# Start A1 A2 A3 Middle B1 B2 End三、生成器表达式生成器表达式是创建生成器的简洁语法类似列表推导式但使用圆括号。它们的外观区别仅在于括号类型但底层行为截然不同——列表推导立即计算所有值并创建完整列表生成器表达式则返回一个生成器对象在你遍历它时才逐个计算值。实际使用建议如果只需要遍历一次数据用生成器表达式如果需要多次访问数据或需要索引访问用列表推导。# 列表推导立即计算一次性创建整个列表占内存list_comp[x**2forxinrange(1000000)]print(f列表推导占用内存:{list_comp.__sizeof__()/1024/1024:.1f}MB)# 生成器表达式惰性计算需要时才生成极省内存gen_expr(x**2forxinrange(1000000))print(f生成器表达式占用内存:{gen_expr.__sizeof__()/1024:.1f}KB)# 逐次取值print(next(gen_expr))# 0print(next(gen_expr))# 1print(next(gen_expr))# 4性能对比importsys data[1,2,3,4,5]# 列表推导创建新列表squares_list[x*xforxindata]# 生成器表达式不创建新列表可以和函数直接配合totalsum(x*xforxindata)# 无需方括号更高效print(f平方和:{total})# 内存占用对比print(f列表推导:{sys.getsizeof(squares_list)}bytes)gen(x*xforxindata)print(f生成器表达式:{sys.getsizeof(gen)}bytes)四、itertools生成器工具箱itertools模块是Python标准库中的隐蔽宝藏提供了大量高效的迭代器构建工具。这些工具都是用C语言实现的性能极高。它们分为三类无限迭代器count、cycle、repeat、有限迭代器accumulate、chain、compress、groupby等、组合迭代器product、permutations、combinations。在实际开发中itertools可以极大简化涉及组合、排列、分组、过滤等操作的代码是每个Python进阶开发者都应该掌握的模块。importitertools# ─── 无限迭代器 ───counteritertools.count(start10,step2)print([next(counter)for_inrange(5)])# [10, 12, 14, 16, 18]cycleditertools.cycle(ABC)print([next(cycled)for_inrange(7)])# [A, B, C, A, B, C, A]repeateditertools.repeat(Hi,3)print(list(repeated))# [Hi, Hi, Hi]# ─── 合并迭代器 ───a[1,2,3]b[a,b,c]# chain: 串联多个迭代器print(list(itertools.chain(a,b)))# [1, 2, 3, a, b, c]# zip_longest: 以最长的为准print(list(itertools.zip_longest(a,b,fillvalueNone)))# [(1, a), (2, b), (3, c)]# ─── 组合生成 ───items[A,B,C]# 排列 (有顺序)print(list(itertools.permutations(items,2)))# [(A, B), (A, C), (B, A), (B, C), (C, A), (C, B)]# 组合 (无顺序)print(list(itertools.combinations(items,2)))# [(A, B), (A, C), (B, C)]# 笛卡尔积colors[红,蓝]sizes[S,M,L]print(list(itertools.product(colors,sizes)))# [(红, S), (红, M), (红, L), (蓝, S), (蓝, M), (蓝, L)]# ─── 过滤与分组 ───data[1,2,3,4,5,6]# filterfalse: 保留不满足条件的print(list(itertools.filterfalse(lambdax:x%20,data)))# [1, 3, 5]# takewhile / dropwhileprint(list(itertools.takewhile(lambdax:x4,data)))# [1, 2, 3]print(list(itertools.dropwhile(lambdax:x4,data)))# [4, 5, 6]# groupby: 按key分组需要数据预先排序students[(北京,张三),(北京,李四),(上海,王五),(上海,赵六),]forcity,groupinitertools.groupby(students,keylambdax:x[0]):names[namefor_,nameingroup]print(f{city}:{, .join(names)})五、生成器的高级用法5.1 双向通信send()方法send()是生成器的高级特性它允许外部世界向生成器内部发送数据实现双向通信。注意在调用send()之前必须先用next()或send(None)将生成器推进到第一个yield表达式处。send()方法将数据传递给yield表达式并返回同时暂停在下一个yield处等待。这个机制是协程的底层实现基础。definteractive_generator():可以与外部双向通信的生成器print(生成器启动)whileTrue:receivedyieldprint(f收到:{received})ifreceivedquit:breakprint(生成器关闭)returnGoodbyegeninteractive_generator()next(gen)# 启动生成器必须先执行到第一个yieldgen.send(Hello)gen.send(World)try:gen.send(quit)exceptStopIterationase:print(f返回值:{e.value})5.2 数据管道生成器的一个重要应用模式是数据管道——将多个生成器串联起来每个生成器完成一步数据处理形成读取→清洗→转换→输出的流式处理链。这种模式的核心优势是内存友好——数据像水流一样在管道中流动每个时刻只有当前正在处理的一小部分数据在内存中。对于处理大数据集数据管道模式比先全部读入再逐个处理的方式更加高效和可靠。“”“生成器1读取文件行模拟”“”lines [“apple,5,2.5”, “banana,3,1.8”, “orange,8,3.0”,“apple,2,2.5”, “banana,6,1.8”, “grape,4,4.0”]for line in lines:yield line.strip()def parse_csv(lines_gen):“”“生成器2解析CSV”“”for line in lines_gen:parts line.split(“,”)yield {“name”: parts[0], “quantity”: int(parts[1]), “price”: float(parts[2])}def calculate_revenue(parsed_gen):“”“生成器3计算收入”“”for item in parsed_gen:item[“revenue”] item[“quantity”] * item[“price”]yield itemdef aggregate_by_name(revenue_gen):“”“生成器4按名称汇总”“”summary {}for item in revenue_gen:name item[“name”]if name not in summary:summary[name] 0summary[name] item[“revenue”]yield from summary.items()管道串联执行pipeline aggregate_by_name(calculate_revenue(parse_csv(read_lines(“data.csv”))))for name, total in pipeline:print(f{name}: ¥{total:.1f})## 六、实战案例大文件处理 处理大文件是生成器最经典的应用场景。当一个文件有几十GB时使用 read() 一次性加载会导致内存溢出——你的程序不是慢而是直接挂掉。使用生成器配合分块读取可以在几乎恒定的内存开销下处理任意大小的文件。下面的案例展示了如何用生成器管道处理一个大型日志文件并提取Top N的IP地址。 python import os import re def read_file_in_chunks(filepath, chunk_size1024 * 1024): 以块的方式读取大文件避免内存溢出 with open(filepath, r, encodingutf-8) as f: while True: chunk f.read(chunk_size) if not chunk: break yield chunk def extract_ips_from_chunks(chunks_gen): 从文件块中提取IP地址 ip_pattern re.compile(r\b(?:\d{1,3}\.){3}\d{1,3}\b) buffer # 处理跨chunk的行 for chunk in chunks_gen: buffer chunk lines buffer.split(\n) # 最后一行可能不完整保留到下次处理 buffer lines.pop() for line in lines: ips ip_pattern.findall(line) for ip in ips: yield ip def top_n(items_gen, n10): 找出Top N的项使用堆更高效此处简化 from collections import Counter counter Counter(items_gen) return counter.most_common(n) # 生成测试文件 test_log_content for i in range(10000): ip f192.168.1.{i % 256} test_log_content f{ip} - [2024-01-15] GET /page/{i}\n with open(big_log.txt, w, encodingutf-8) as f: f.write(test_log_content) # 使用管道处理大文件 chunks read_file_in_chunks(big_log.txt, chunk_size4096) ips extract_ips_from_chunks(chunks) top_ips top_n(ips, 5) print(访问最多的IP (Top 5):) for ip, count in top_ips: print(f {ip}: {count}次)七、迭代器协议检查与常见坑迭代器有一个容易让人忽略的特点生成器只能遍历一次。一旦生成器的所有值都被消费再次遍历它将不会产生任何值。这在调试时常常造成困惑——你打印了一次生成器的内容list(gen)然后试图再次遍历它发现它是空的。解决方案很简单如果需要多次遍历使用列表存储结果或者每次都重新创建生成器。deffibonacci(n):斐波那契数列生成器a,b0,1for_inrange(n):yielda a,bb,ab# 注意生成器只能遍历一次fibfibonacci(5)print(第一次遍历:)print(list(fib))# [0, 1, 1, 2, 3]print(第二次遍历:)print(list(fib))# [] —— 生成器已耗尽# 需要重复遍历时应使用函数调用重新创建print(重新创建生成器:)print(list(fibonacci(5)))# [0, 1, 1, 2, 3]总结生成器和迭代器是Python中处理序列数据的核心机制。它们不仅是内存管理的利器更是Python异步编程和协程的基石——yield就是协程的雏形理解生成器的工作方式是掌握asyncio的前提。特性列表生成器内存占用一次性存储所有元素按需生成极省内存访问方式可多次随机访问只能遍历一次计算时机立即惰性求值适用场景小数据需多次访问大数据流一次处理iter()/next()实现迭代器协议yield轻松创建生成器yield from委托子生成器生成器表达式(x for x in ...)是列表推导的内存友好版itertools模块是处理迭代器的瑞士军刀下一篇我们将探索时间日期处理学习如何使用datetime模块优雅地处理所有与时间相关的操作。✅ 亮点总结从迭代器协议__iter__/__next__底层原理讲起揭示 for 循环的幕后机制yield关键字的核心用法全解普通生成器、yield from委托、send()协程通信生成器表达式作为内存友好型列表推导适合大数据流的链式处理itertools模块 7 大常用工具count/cycle/chain/islice/groupby/combinations/product实战演示适用场景大文件流式处理逐行读取海量日志文件而不占用过多内存无限序列生成生成斐波那契数列、素数序列等理论上无限的数学序列数据管道构建将多个生成器串联实现读取→清洗→转换→输出的流式处理链扩展方向学习 Python 3.3 的yield from高级用法实现协程嵌套和双向通信探索collections.abc中的迭代器抽象基类构建自定义容器类型结合 asyncio 的异步生成器async for实现异步流式数据处理