17 - 推导式与生成器推导式是 Python 最优雅的特性之一一行代码顶一个循环。生成器则是处理大数据时的利器——用多少算多少不浪费内存。列表推导式前面已经见过好几次了这里系统讲一下。基本语法[表达式for变量in可迭代对象]# 不用推导式squares[]forxinrange(1,6):squares.append(x**2)# 用推导式squares[x**2forxinrange(1,6)]print(squares)# [1, 4, 9, 16, 25]加条件过滤[表达式for变量in可迭代对象if条件]# 偶数的平方evens[x**2forxinrange(1,11)ifx%20]print(evens)# [4, 16, 36, 64, 100]# 过滤空字符串words[hello,,world,,python]non_empty[wforwinwordsifw]print(non_empty)# [hello, world, python]加 if-else条件表达式# if-else 放在 for 前面[表达式1if条件else表达式2for变量in可迭代对象]# 大于 0 的保留其他的变成 0numbers[-3,-1,0,2,5]result[nifn0else0forninnumbers]print(result)# [0, 0, 0, 2, 5]注意if放在for后面是过滤元素可能减少放在for前面是条件表达式元素数量不变只是值变了。嵌套循环# 展平二维列表matrix[[1,2,3],[4,5,6],[7,8,9]]flat[xforrowinmatrixforxinrow]print(flat)# [1, 2, 3, 4, 5, 6, 7, 8, 9]# 等价于flat[]forrowinmatrix:forxinrow:flat.append(x)嵌套推导式的顺序是外层在前内层在后跟嵌套循环的书写顺序一致。不过说实话超过两层嵌套的推导式就很难读了。别为了炫技写一行几百字符的代码该拆就拆。实际用法# 读取文件的非空行lines[line.strip()forlineinopen(data.txt)ifline.strip()]# 字符串列表转整数numbers[int(s)forsin[1,2,3]]# 提取字典的某些值scores{小明:85,小红:92,小刚:78}high_scores{name:scoreforname,scoreinscores.items()ifscore80}# 生成坐标对coords[(x,y)forxinrange(3)foryinrange(3)]# [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]字典推导式跟列表推导式类似只是用花括号和键值对# 创建平方数字典squares{x:x**2forxinrange(1,6)}print(squares)# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}# 反转字典original{a:1,b:2,c:3}reversed_dict{v:kfork,vinoriginal.items()}print(reversed_dict)# {1: a, 2: b, 3: c}# 过滤scores{小明:85,小红:92,小刚:78}passed{name:scoreforname,scoreinscores.items()ifscore80}print(passed)# {小明: 85, 小红: 92}# 统计字符出现次数texthello worldchar_count{c:text.count(c)forcinset(text)}集合推导式跟列表推导式一样但用花括号结果自动去重# 去重words[hello,world,hello,python]unique_lengths{len(w)forwinwords}print(unique_lengths)# {5, 6}# 取所有不同的首字母first_letters{w[0]forwinwords}print(first_letters)# {h, w, p}生成器表达式生成器表达式跟列表推导式几乎一样区别是用圆括号()而不是方括号[]# 列表推导式 — 立即生成整个列表占内存squares_list[x**2forxinrange(1000000)]# 生成器表达式 — 惰性计算用多少算多少squares_gen(x**2forxinrange(1000000))生成器不会一次性把所有值算出来而是你问它要的时候才算下一个gen(x**2forxinrange(5))print(next(gen))# 0print(next(gen))# 1print(next(gen))# 4print(next(gen))# 9print(next(gen))# 16# print(next(gen)) # StopIteration! 没有更多了通常用 for 循环来消费生成器gen(x**2forxinrange(5))forvalingen:print(val)什么时候用生成器处理大量数据时。比如读一个大文件# 不好——一次把所有行读进内存lines[line.strip()forlineinopen(big_file.txt)]# 好——逐行处理内存只存一行lines(line.strip()forlineinopen(big_file.txt))forlineinlines:process(line)# sum/max/min 等函数可以直接接收生成器totalsum(x**2forxinrange(1000000))# 不需要方括号注意生成器只能用一次。遍历完了就没了gen(xforxinrange(3))print(list(gen))# [0, 1, 2]print(list(gen))# []已经用完了yield 关键字如果你想写一个更复杂的生成器可以用函数加yielddefcountdown(n):倒计时生成器whilen0:yieldn n-1fornumincountdown(5):print(num)# 5 4 3 2 1yield跟return类似都是返回一个值。但yield会暂停函数下次调用时从暂停的地方继续。return是直接结束函数。执行过程defmy_gen():print(开始)yield1print(第二次)yield2print(第三次)yield3print(结束)genmy_gen()print(next(gen))# 输出 开始然后 1print(next(gen))# 输出 第二次然后 2print(next(gen))# 输出 第三次然后 3print(next(gen))# 输出 结束然后 StopIteration实际例子deffibonacci():无限斐波那契数列a,b0,1whileTrue:yielda a,bb,ab# 取前 10 个fibfibonacci()for_inrange(10):print(next(fib),end )# 0 1 1 2 3 5 8 13 21 34defread_large_file(filepath):逐行读取大文件withopen(filepath,encodingutf-8)asf:forlineinf:yieldline.strip()forlineinread_large_file(big_data.csv):process(line)defbatch_processor(items,batch_size):分批处理batch[]foriteminitems:batch.append(item)iflen(batch)batch_size:yieldbatch batch[]ifbatch:yieldbatch# 每 3 个一批forbatchinbatch_processor(range(10),3):print(batch)# [0, 1, 2]# [3, 4, 5]# [6, 7, 8]# [9]生成器的 send 方法这个比较高级了解就行。send()可以往生成器里面传值defaccumulator():total0whileTrue:valueyieldtotal totalvalue accaccumulator()next(acc)# 启动生成器必须先调一次 nextprint(acc.send(10))# 10print(acc.send(20))# 30print(acc.send(5))# 35yield不仅返回值还能接收值。这种双向通信的生成器叫协程coroutine是 Python 异步编程的基础之一。不过日常开发中用得不多。迭代器协议生成器之所以能用 for 循环遍历是因为它实现了迭代器协议——有__iter__和__next__方法。你也可以自己实现classCountDown:def__init__(self,start):self.currentstartdef__iter__(self):returnselfdef__next__(self):ifself.current0:raiseStopIteration self.current-1returnself.current1fornuminCountDown(5):print(num)# 5 4 3 2 1大多数时候用yield写生成器就够了不需要自己实现迭代器协议。本章小结列表推导式[表达式 for x in 序列 if 条件]是写循环的优雅方式字典推导式{k: v for ...}和集合推导式{x for ...}语法类似生成器表达式(表达式 for x in 序列)惰性计算省内存yield关键字让函数变成生成器可以逐个产出值生成器只能遍历一次处理大数据、无限序列时优先考虑生成器面试题Q1列表推导式和生成器表达式有什么区别点击查看答案列表推导式[...]生成器表达式(...)内存立即生成全部元素惰性计算按需生成速度创建慢访问快创建快每次访问需计算可重复遍历可以只能遍历一次支持索引可以lst[i]不可以选择数据量小用列表推导式方便索引和重复访问数据量大用生成器表达式省内存。Q2yield和return有什么区别点击查看答案return结束函数执行返回一个值yield暂停函数执行返回一个值。下次next()时从暂停处继续用yield的函数叫生成器函数调用它不会执行函数体而是返回一个生成器对象。deff():yield1yield2genf()# 不执行函数体next(gen)# 执行到第一个 yield返回 1next(gen)# 从第一个 yield 后继续返回 2yield适合产出一系列值return适合返回单个结果。Q3为什么生成器只能遍历一次如何多次遍历点击查看答案生成器是惰性计算的迭代器每次next()推进内部状态到达StopIteration后就耗尽了无法重置。多次遍历的方案重新创建生成器每次需要时调用生成器函数转为列表data list(gen)但会消耗内存用 itertools.teegen1, gen2 itertools.tee(gen, 2)创建多个独立副本Q4如何用生成器处理大文件为什么比列表好点击查看答案defread_lines(filepath):withopen(filepath)asf:forlineinf:yieldline.strip()forlineinread_lines(big_file.txt):process(line)比列表好的原因内存列表一次加载所有行到内存生成器每次只保留一行启动速度列表需要读完整个文件才能开始处理生成器读到第一行就可以开始可以处理无限流生成器不需要全部读完对于 GB 级别的文件列表可能导致内存不足OOM生成器没有这个问题。
17 - 推导式与生成器
发布时间:2026/5/28 22:31:40
17 - 推导式与生成器推导式是 Python 最优雅的特性之一一行代码顶一个循环。生成器则是处理大数据时的利器——用多少算多少不浪费内存。列表推导式前面已经见过好几次了这里系统讲一下。基本语法[表达式for变量in可迭代对象]# 不用推导式squares[]forxinrange(1,6):squares.append(x**2)# 用推导式squares[x**2forxinrange(1,6)]print(squares)# [1, 4, 9, 16, 25]加条件过滤[表达式for变量in可迭代对象if条件]# 偶数的平方evens[x**2forxinrange(1,11)ifx%20]print(evens)# [4, 16, 36, 64, 100]# 过滤空字符串words[hello,,world,,python]non_empty[wforwinwordsifw]print(non_empty)# [hello, world, python]加 if-else条件表达式# if-else 放在 for 前面[表达式1if条件else表达式2for变量in可迭代对象]# 大于 0 的保留其他的变成 0numbers[-3,-1,0,2,5]result[nifn0else0forninnumbers]print(result)# [0, 0, 0, 2, 5]注意if放在for后面是过滤元素可能减少放在for前面是条件表达式元素数量不变只是值变了。嵌套循环# 展平二维列表matrix[[1,2,3],[4,5,6],[7,8,9]]flat[xforrowinmatrixforxinrow]print(flat)# [1, 2, 3, 4, 5, 6, 7, 8, 9]# 等价于flat[]forrowinmatrix:forxinrow:flat.append(x)嵌套推导式的顺序是外层在前内层在后跟嵌套循环的书写顺序一致。不过说实话超过两层嵌套的推导式就很难读了。别为了炫技写一行几百字符的代码该拆就拆。实际用法# 读取文件的非空行lines[line.strip()forlineinopen(data.txt)ifline.strip()]# 字符串列表转整数numbers[int(s)forsin[1,2,3]]# 提取字典的某些值scores{小明:85,小红:92,小刚:78}high_scores{name:scoreforname,scoreinscores.items()ifscore80}# 生成坐标对coords[(x,y)forxinrange(3)foryinrange(3)]# [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]字典推导式跟列表推导式类似只是用花括号和键值对# 创建平方数字典squares{x:x**2forxinrange(1,6)}print(squares)# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}# 反转字典original{a:1,b:2,c:3}reversed_dict{v:kfork,vinoriginal.items()}print(reversed_dict)# {1: a, 2: b, 3: c}# 过滤scores{小明:85,小红:92,小刚:78}passed{name:scoreforname,scoreinscores.items()ifscore80}print(passed)# {小明: 85, 小红: 92}# 统计字符出现次数texthello worldchar_count{c:text.count(c)forcinset(text)}集合推导式跟列表推导式一样但用花括号结果自动去重# 去重words[hello,world,hello,python]unique_lengths{len(w)forwinwords}print(unique_lengths)# {5, 6}# 取所有不同的首字母first_letters{w[0]forwinwords}print(first_letters)# {h, w, p}生成器表达式生成器表达式跟列表推导式几乎一样区别是用圆括号()而不是方括号[]# 列表推导式 — 立即生成整个列表占内存squares_list[x**2forxinrange(1000000)]# 生成器表达式 — 惰性计算用多少算多少squares_gen(x**2forxinrange(1000000))生成器不会一次性把所有值算出来而是你问它要的时候才算下一个gen(x**2forxinrange(5))print(next(gen))# 0print(next(gen))# 1print(next(gen))# 4print(next(gen))# 9print(next(gen))# 16# print(next(gen)) # StopIteration! 没有更多了通常用 for 循环来消费生成器gen(x**2forxinrange(5))forvalingen:print(val)什么时候用生成器处理大量数据时。比如读一个大文件# 不好——一次把所有行读进内存lines[line.strip()forlineinopen(big_file.txt)]# 好——逐行处理内存只存一行lines(line.strip()forlineinopen(big_file.txt))forlineinlines:process(line)# sum/max/min 等函数可以直接接收生成器totalsum(x**2forxinrange(1000000))# 不需要方括号注意生成器只能用一次。遍历完了就没了gen(xforxinrange(3))print(list(gen))# [0, 1, 2]print(list(gen))# []已经用完了yield 关键字如果你想写一个更复杂的生成器可以用函数加yielddefcountdown(n):倒计时生成器whilen0:yieldn n-1fornumincountdown(5):print(num)# 5 4 3 2 1yield跟return类似都是返回一个值。但yield会暂停函数下次调用时从暂停的地方继续。return是直接结束函数。执行过程defmy_gen():print(开始)yield1print(第二次)yield2print(第三次)yield3print(结束)genmy_gen()print(next(gen))# 输出 开始然后 1print(next(gen))# 输出 第二次然后 2print(next(gen))# 输出 第三次然后 3print(next(gen))# 输出 结束然后 StopIteration实际例子deffibonacci():无限斐波那契数列a,b0,1whileTrue:yielda a,bb,ab# 取前 10 个fibfibonacci()for_inrange(10):print(next(fib),end )# 0 1 1 2 3 5 8 13 21 34defread_large_file(filepath):逐行读取大文件withopen(filepath,encodingutf-8)asf:forlineinf:yieldline.strip()forlineinread_large_file(big_data.csv):process(line)defbatch_processor(items,batch_size):分批处理batch[]foriteminitems:batch.append(item)iflen(batch)batch_size:yieldbatch batch[]ifbatch:yieldbatch# 每 3 个一批forbatchinbatch_processor(range(10),3):print(batch)# [0, 1, 2]# [3, 4, 5]# [6, 7, 8]# [9]生成器的 send 方法这个比较高级了解就行。send()可以往生成器里面传值defaccumulator():total0whileTrue:valueyieldtotal totalvalue accaccumulator()next(acc)# 启动生成器必须先调一次 nextprint(acc.send(10))# 10print(acc.send(20))# 30print(acc.send(5))# 35yield不仅返回值还能接收值。这种双向通信的生成器叫协程coroutine是 Python 异步编程的基础之一。不过日常开发中用得不多。迭代器协议生成器之所以能用 for 循环遍历是因为它实现了迭代器协议——有__iter__和__next__方法。你也可以自己实现classCountDown:def__init__(self,start):self.currentstartdef__iter__(self):returnselfdef__next__(self):ifself.current0:raiseStopIteration self.current-1returnself.current1fornuminCountDown(5):print(num)# 5 4 3 2 1大多数时候用yield写生成器就够了不需要自己实现迭代器协议。本章小结列表推导式[表达式 for x in 序列 if 条件]是写循环的优雅方式字典推导式{k: v for ...}和集合推导式{x for ...}语法类似生成器表达式(表达式 for x in 序列)惰性计算省内存yield关键字让函数变成生成器可以逐个产出值生成器只能遍历一次处理大数据、无限序列时优先考虑生成器面试题Q1列表推导式和生成器表达式有什么区别点击查看答案列表推导式[...]生成器表达式(...)内存立即生成全部元素惰性计算按需生成速度创建慢访问快创建快每次访问需计算可重复遍历可以只能遍历一次支持索引可以lst[i]不可以选择数据量小用列表推导式方便索引和重复访问数据量大用生成器表达式省内存。Q2yield和return有什么区别点击查看答案return结束函数执行返回一个值yield暂停函数执行返回一个值。下次next()时从暂停处继续用yield的函数叫生成器函数调用它不会执行函数体而是返回一个生成器对象。deff():yield1yield2genf()# 不执行函数体next(gen)# 执行到第一个 yield返回 1next(gen)# 从第一个 yield 后继续返回 2yield适合产出一系列值return适合返回单个结果。Q3为什么生成器只能遍历一次如何多次遍历点击查看答案生成器是惰性计算的迭代器每次next()推进内部状态到达StopIteration后就耗尽了无法重置。多次遍历的方案重新创建生成器每次需要时调用生成器函数转为列表data list(gen)但会消耗内存用 itertools.teegen1, gen2 itertools.tee(gen, 2)创建多个独立副本Q4如何用生成器处理大文件为什么比列表好点击查看答案defread_lines(filepath):withopen(filepath)asf:forlineinf:yieldline.strip()forlineinread_lines(big_file.txt):process(line)比列表好的原因内存列表一次加载所有行到内存生成器每次只保留一行启动速度列表需要读完整个文件才能开始处理生成器读到第一行就可以开始可以处理无限流生成器不需要全部读完对于 GB 级别的文件列表可能导致内存不足OOM生成器没有这个问题。