资讯中心

【python零基础教程第11讲】Python 迭代器与生成器

📅 2026/7/9 18:05:29
【python零基础教程第11讲】Python 迭代器与生成器
Python 迭代器与生成器从协议到高级应用的全面指南引言在 Python 的编程哲学中迭代是一个核心概念。无论是遍历列表、读取文件还是处理流式数据迭代器与生成器都扮演着不可或缺的角色。它们不仅让代码更简洁、更 Pythonic还通过惰性求值机制大幅优化了内存使用。然而许多开发者对迭代器协议、yield的底层原理、生成器与列表推导式的内存差异以及itertools的高级用法仍存在模糊认识。本文将从迭代器协议的完整规则出发逐步深入到生成器函数、生成器表达式、内存对比、生成器高级操作send、close、throw最后探索itertools 工具库的无限迭代、分组与笛卡尔积等实用技巧。通过大量代码示例与原理剖析帮助你彻底掌握这一 Python 进阶必备技能。一、迭代器协议Python 迭代的基石1.1 什么是迭代器协议在 Python 中迭代器协议是一套约定任何对象只要实现了以下两个方法就可以被for循环迭代或用于iter()、next()等内置函数__iter__()返回迭代器对象自身或返回一个新的迭代器。对于容器类如列表、元组__iter__返回一个迭代器实例。__next__()返回容器中的下一个元素。如果没有更多元素则抛出StopIteration异常。注意可迭代对象Iterable与迭代器Iterator的区别可迭代对象实现了__iter__()方法返回一个迭代器。例如列表、字符串、字典。迭代器同时实现了__iter__()和__next__()方法。迭代器可以记住遍历的位置且只能向前不能后退。1.2 手动实现一个迭代器让我们通过一个简单的例子来理解协议classCountDown:从 n 倒数到 0 的迭代器def__init__(self,n):self.nndef__iter__(self):# 返回迭代器自身因为 CountDown 实例本身就是迭代器returnselfdef__next__(self):ifself.n0:raiseStopIteration currentself.n self.n-1returncurrent# 使用fornuminCountDown(5):print(num)# 输出 5 4 3 2 1 0关键点__iter__返回self意味着该对象既是可迭代对象也是迭代器。__next__每次调用返回当前值并更新状态当无元素时抛出StopIteration。for循环内部会自动捕获StopIteration并终止循环。1.3 内置函数iter()与next()iter(obj)调用obj.__iter__()获取迭代器。next(iterator)调用iterator.__next__()获取下一个元素可指定默认值避免异常。lst[1,2,3]ititer(lst)print(next(it))# 1print(next(it))# 2print(next(it))# 3print(next(it,END))# END不会抛出 StopIteration1.4 迭代器协议完整规则总结方法作用返回值异常__iter__(self)返回迭代器对象迭代器对象通常为self无__next__(self)返回下一个元素下一个元素StopIteration无更多元素重要规则迭代器对象必须实现__iter__和__next__。可迭代对象只需实现__iter__返回一个迭代器。迭代器是一次性的遍历完所有元素后再次调用next()会持续抛出StopIteration。迭代器可以包含无限元素如itertools.count此时for循环需手动 break。二、生成器优雅的迭代器工厂2.1 生成器函数与yield生成器函数是包含yield关键字的函数。当调用生成器函数时它不会立即执行函数体而是返回一个生成器对象属于迭代器的一种。每次调用next()时函数会从上次暂停的位置继续执行直到遇到下一个yield或函数结束。deffibonacci(limit):a,b0,1whilealimit:yielda a,bb,ab genfibonacci(10)fornumingen:print(num)# 0 1 1 2 3 5 8yield与return的区别return终止函数并返回一个值函数状态被销毁。yield暂停函数保存当前所有局部变量状态返回一个值下次调用时恢复。2.2 惰性取值Lazy Evaluation生成器的核心优势是惰性求值只在需要时才计算下一个值而不是一次性生成所有结果。这对于处理大规模数据或无限序列至关重要。# 对比列表推导式一次性生成所有平方数squares_list[x**2forxinrange(1000000)]# 占用大量内存# 生成器表达式惰性生成squares_gen(x**2forxinrange(1000000))# 几乎不占内存print(next(squares_gen))# 0print(next(squares_gen))# 1应用场景读取大文件逐行读取避免内存爆炸流式数据处理如网络数据包、传感器数据无限序列如自然数、斐波那契数列2.3 生成器表达式 vs 列表推导式内存差异深度对比特性列表推导式[expr for item in iterable]生成器表达式(expr for item in iterable)返回类型列表list生成器对象generator内存占用一次性生成所有元素占用 O(n) 内存惰性求值只保存当前状态占用 O(1) 内存执行时机定义时立即计算定义时不计算迭代时逐个计算可迭代次数可多次迭代只能迭代一次迭代器特性速度生成列表稍快但内存开销大迭代时每次计算可能稍慢但内存友好适用场景数据量小、需要多次访问、需要索引数据量大、只需遍历一次、无限序列内存实测示例importsys# 列表推导式list_comp[iforiinrange(1000000)]print(sys.getsizeof(list_comp))# 约 8,000,056 字节8MB# 生成器表达式gen_expr(iforiinrange(1000000))print(sys.getsizeof(gen_expr))# 约 112 字节几乎不变注意生成器表达式虽然内存小但每次迭代都有函数调用开销。如果数据量小且需要多次随机访问列表推导式更合适。三、生成器高级操作send()、close()、throw()生成器不仅仅是单向的迭代器它还支持与调用者双向通信。通过send()、close()、throw()方法我们可以向生成器内部发送值、终止生成器或注入异常。3.1send(value)向生成器发送值send()是next()的增强版它除了让生成器继续执行外还能将value作为yield表达式的返回值传递给生成器内部。注意第一次调用生成器时必须使用next(gen)或gen.send(None)来启动因为生成器尚未到达第一个yield。defecho():print(开始)whileTrue:receivedyield# yield 表达式可以接收值print(f收到:{received})genecho()next(gen)# 启动生成器输出 开始gen.send(Hello)# 输出 收到: Hellogen.send(World)# 输出 收到: Worldgen.close()# 关闭生成器典型应用协程Coroutine的雏形用于实现简单的生产者-消费者模式。3.2close()关闭生成器调用gen.close()会在生成器当前暂停的yield处抛出GeneratorExit异常。如果生成器内部捕获该异常并执行清理操作则允许否则生成器正常终止。之后再次调用next()或send()会抛出StopIteration。defresource_handler():try:print(资源打开)yieldexceptGeneratorExit:print(资源清理)raise# 必须重新抛出否则会报 RuntimeErrorgenresource_handler()next(gen)# 输出 资源打开gen.close()# 输出 资源清理3.3throw(type, valueNone, tracebackNone)向生成器注入异常throw()在生成器当前暂停处抛出指定异常。如果生成器内部捕获并处理了该异常则继续执行否则异常会传播给调用者。defsafe_divide():try:xyieldyyieldresultx/yyieldresultexceptZeroDivisionError:yield除数不能为零gensafe_divide()next(gen)# 启动gen.send(10)# 发送 x10gen.throw(ZeroDivisionError)# 注入异常输出 除数不能为零注意throw()返回生成器下一次yield的值如果异常被捕获并继续执行否则异常会向上传播。四、itertools 工具库迭代的瑞士军刀itertools是 Python 标准库中专门用于操作迭代器的模块提供了大量高效、内存友好的工具函数。以下重点介绍无限迭代、分组和笛卡尔积。4.1 无限迭代器count(start0, step1)生成从start开始步长为step的无限整数序列。cycle(iterable)无限循环迭代iterable中的元素。repeat(elem, timesNone)无限重复elem若指定times则重复有限次。fromitertoolsimportcount,cycle,repeat# 无限自然数foriincount(1):ifi5:breakprint(i)# 1 2 3 4 5# 循环颜色colorscycle([red,green,blue])for_inrange(6):print(next(colors))# red green blue red green blue# 重复值forvalinrepeat(A,3):print(val)# A A A应用生成唯一 ID、轮询任务、测试数据填充。4.2 分组groupby()groupby(iterable, keyNone)将连续的元素分组返回一个生成器每个元素为(key, group_iterator)。注意groupby只对已排序的序列有效因为它只比较相邻元素。fromitertoolsimportgroupby data[(apple,3),(banana,2),(apple,5),(banana,1)]# 先按水果名排序sorted_datasorted(data,keylambdax:x)forfruit,groupingroupby(sorted_data,keylambdax:x):print(fruit,list(group))# 输出# apple [(apple, 3), (apple, 5)]# banana [(banana, 2), (banana, 1)]高级用法结合itemgetter或自定义 key 函数可用于日志分析、数据聚合等。4.3 笛卡尔积product()product(*iterables, repeat1)计算多个可迭代对象的笛卡尔积相当于嵌套的for循环。repeat参数允许重复使用同一迭代器。fromitertoolsimportproduct# 两个列表的笛卡尔积forpinproduct([1,2],[a,b]):print(p)# (1, a) (1, b) (2, a) (2, b)# 重复使用同一个迭代器相当于自身笛卡尔积forpinproduct(AB,repeat2):print(p)# (A,A) (A,B) (B,A) (B,B)应用生成所有可能的参数组合测试用例、密码暴力破解、排列组合。4.4 其他常用 itertools 函数速览函数作用示例chain(*iterables)将多个迭代器串联成一个chain([1,2], [3,4])→ 1,2,3,4compress(data, selectors)根据选择器过滤数据compress(ABCD, [1,0,1,0])→ A,Cdropwhile(pred, seq)跳过满足条件的开头元素dropwhile(lambda x: x5, [1,4,6,3])→ 6,3takewhile(pred, seq)取满足条件的开头元素takewhile(lambda x: x5, [1,4,6,3])→ 1,4islice(seq, start, stop, step)对迭代器切片islice(range(10), 2, 8, 2)→ 2,4,6zip_longest(*iterables, fillvalue)类似 zip但以最长迭代器为准zip_longest(AB, 123, fillvalue?)→ (A,1),(B,2),(?,3)五、实战案例结合生成器与 itertools 处理大文件假设我们有一个巨大的日志文件access.log每行格式为IP 时间 请求需要统计每个 IP 的请求次数并输出前 10 个最活跃的 IP。使用生成器逐行读取避免内存爆炸。fromitertoolsimportgroupby,islicefromoperatorimportitemgetterdefread_lines(file_path):生成器逐行读取文件withopen(file_path,r)asf:forlineinf:yieldline.strip()defparse_ip(line):提取 IP 地址假设第一列returnline.split()# 1. 读取所有行惰性linesread_lines(access.log)# 2. 提取 IP 并排序groupby 需要排序ips(parse_ip(line)forlineinlines)sorted_ipssorted(ips)# 注意sorted 会一次性加载所有 IP但这里 IP 数量远小于文件行数实际上仍可能大可优化# 3. 分组统计ip_counts((ip,len(list(group)))forip,groupingroupby(sorted_ips))# 4. 按次数降序排序取前10top10sorted(ip_counts,keyitemgetter(1),reverseTrue)[:10]forip,countintop10:print(f{ip}:{count})优化如果 IP 数量也很大可以使用collections.Counter结合生成器但Counter内部也是字典内存占用可控。更极致的方案是使用heapq.nlargest配合生成器。六、总结与最佳实践6.1 何时使用迭代器/生成器数据量巨大无法一次性加载到内存时使用生成器惰性处理。无限序列如自然数、传感器数据流。管道式处理将多个生成器串联形成数据处理流水线。协程与异步生成器是协程的基础Python 3.5 后async/await更主流但生成器仍用于简单场景。6.2 性能注意事项生成器每次迭代都有函数调用开销对于小数据集列表推导式可能更快。避免在生成器内部进行复杂计算否则会拖慢迭代速度。使用itertools函数通常比手动实现更高效C 语言实现。6.3 常见陷阱生成器只能迭代一次如果需要多次遍历请转换为列表或重新创建生成器。groupby需要排序否则结果不符合预期。send()第一次必须传None否则会抛出TypeError。生成器中的return语句在 Python 3.3 中return value会将value作为StopIteration的参数可通过gen.value获取但很少用。结语迭代器与生成器是 Python 中优雅与高效的完美结合。掌握它们你不仅能写出更节省内存的代码还能以函数式编程的思维构建复杂的数据处理管道。从今天开始尝试用生成器替换那些不必要的列表推导式你会发现 Python 的世界更加广阔。