大家好,我是程序员晚枫。

生成器是 Python 的"黑科技"。

不会用生成器,你的代码"浪费"内存。

今天彻底讲透。


一、什么是生成器?

生成器 = "懒人函数"

普通函数:一次性返回所有结果

生成器一次只返回一个,需要时再算下一个

通俗解释

普通函数像一次性买一年的菜

1
2
3
# 一次性返回所有 365 天的菜
def all_meals():
return [f"第{i}天菜" for i in range(365)]

生成器像每天去菜市场

1
2
3
4
# 每天返回一个
def daily_meal():
for i in range(365):
yield f"第{i}天菜"

生成器的好处

  • 内存省:不用一次性存所有
  • 速度快:不用等全部算完
  • 可处理无限数据

二、5 个生成器基础

基础 1:yield 创建生成器

1
2
3
4
5
6
7
8
9
def my_gen():
yield 1
yield 2
yield 3

g = my_gen()
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3

基础 2:生成器是迭代器

可以用 for 循环

1
2
3
4
5
for x in my_gen():
print(x)
# 1
# 2
# 3

基础 3:生成器只能迭代一次

1
2
3
g = my_gen()
list(g) # [1, 2, 3]
list(g) # [](已经耗尽)

基础 4:生成器有状态

yield 暂停,下次 next 从暂停处继续

基础 5:StopIteration

迭代完抛出 StopIteration

1
2
3
4
5
g = my_gen()
next(g) # 1
next(g) # 2
next(g) # 3
next(g) # StopIteration

三、生成器 vs 列表(性能对比)

内存对比

1
2
3
4
5
6
7
8
9
import sys

# 列表
list_comp = [i ** 2 for i in range(1_000_000)]
print(sys.getsizeof(list_comp)) # 约 8 MB

# 生成器
gen_comp = (i ** 2 for i in range(1_000_000))
print(sys.getsizeof(gen_comp)) # 约 200 B

内存省 4 万倍

速度对比

1
2
3
4
5
6
7
8
9
10
11
import time

# 列表
start = time.time()
list_comp = [i ** 2 for i in range(1_000_000)]
print(time.time() - start) # 0.1 秒

# 生成器
start = time.time()
gen_comp = (i ** 2 for i in range(1_000_000))
print(time.time() - start) # 0.0001 秒

快 1000 倍

5 大对比

维度列表生成器
内存
速度(创建)
速度(迭代)慢一点
可重复迭代
支持 len()
支持索引

生成器适合"大数据"


四、5 大生成器表达式

表达式 1:基础

1
gen = (x ** 2 for x in range(10))

表达式 2:带 if

1
gen = (x for x in range(10) if x % 2 == 0)

表达式 3:带函数

1
gen = (str(x) for x in range(10))

表达式 4:嵌套

1
gen = (x * y for x in range(3) for y in range(3))

表达式 5:复杂

1
2
3
4
5
gen = (
process(item)
for item in items
if condition(item)
)

五、5 大生成器高级用法

高级 1:send() 发送值

1
2
3
4
5
6
7
8
9
10
def my_gen():
value = yield 1
print(f"Got: {value}")
value = yield 2
print(f"Got: {value}")

g = my_gen()
next(g) # 1
g.send("Hello") # Got: Hello
g.send("World") # Got: World

高级 2:throw() 抛异常

1
2
3
4
5
6
7
8
9
10
def my_gen():
try:
yield 1
yield 2
except ValueError:
print("Got ValueError")

g = my_gen()
next(g) # 1
g.throw(ValueError) # Got ValueError

高级 3:close() 关闭

1
2
3
4
5
6
7
8
9
10
def my_gen():
try:
yield 1
yield 2
except GeneratorExit:
print("Closing")

g = my_gen()
next(g) # 1
g.close() # Closing

高级 4:yield from 委托

1
2
3
4
5
6
7
8
9
10
def gen1():
yield 1
yield 2

def gen2():
yield from gen1() # 委托给 gen1
yield 3
yield 4

list(gen2()) # [1, 2, 3, 4]

高级 5:async 生成器

1
2
3
4
5
6
7
8
9
10
11
12
import asyncio

async def async_gen():
for i in range(3):
await asyncio.sleep(1)
yield i

async def main():
async for x in async_gen():
print(x)

asyncio.run(main())

六、5 大真实场景

场景 1:读大文件

普通方式(占用 1GB 内存):

1
2
3
4
with open('big.txt') as f:
lines = f.readlines() # 一次性读所有
for line in lines:
process(line)

生成器方式(占用 1KB 内存):

1
2
3
4
5
6
7
def read_lines(file):
with open(file) as f:
for line in f:
yield line

for line in read_lines('big.txt'):
process(line)

内存省 100 万倍

场景 2:数据流处理

1
2
3
4
5
6
def data_stream():
for record in get_data():
yield transform(record)

for data in data_stream():
save(data)

场景 3:无限序列

1
2
3
4
5
6
7
8
9
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b

g = fib()
for i in range(10):
print(next(g)) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

场景 4:管道(pipeline)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def read_data():
with open('data.txt') as f:
for line in f:
yield line

def parse_data(lines):
for line in lines:
yield line.strip().split(',')

def process_data(records):
for record in records:
yield [int(x) for x in record]

# 管道式处理
data = process_data(parse_data(read_data()))
for record in data:
print(record)

场景 5:爬虫分页

1
2
3
4
5
6
7
8
9
10
11
12
def fetch_pages():
page = 1
while True:
data = api.get(f'?page={page}')
if not data:
break
yield data
page += 1

for items in fetch_pages():
for item in items:
process(item)

七、5 大常见误区

误区 1:生成器 = 列表

  • ❌ 错
  • 生成器是迭代器

误区 2:生成器可以重复

  • ❌ 错
  • 只能迭代一次

误区 3:生成器可以 len()

  • ❌ 错
  • 没有 len()

误区 4:生成器快

  • ⚠️ 部分对
  • ✅ 创建快,迭代慢
  • 大数据用生成器

误区 5:生成器很难

  • ⚠️ 部分对
  • ✅ yield 概念
  • 1 周上手

八、生成器 vs 列表推导式

列表推导式 [...]

1
2
# 一次性计算
squares = [x ** 2 for x in range(10)]

生成器表达式 (...)

1
2
# 懒计算
squares = (x ** 2 for x in range(10))

怎么选?

  • 小数据 + 多次用:列表推导式
  • 大数据 + 一次用:生成器
  • 无限数据:生成器
  • 需要 len/index/slice:列表

九、5 个生成器实战项目

项目 1:日志分析器

1
2
3
4
5
6
7
8
def read_log(file):
with open(file) as f:
for line in f:
yield parse(line)

for log in read_log('huge.log'):
if 'ERROR' in log['level']:
send_alert(log)

项目 2:CSV 处理

1
2
3
4
5
6
7
8
9
10
import csv

def read_csv(file):
with open(file) as f:
reader = csv.DictReader(f)
for row in reader:
yield row

for row in read_csv('big.csv'):
save_to_db(row)

项目 3:API 分页

1
2
3
4
5
6
7
8
def fetch_all_pages(api):
page = 1
while True:
data = api.get(f'?page={page}')
if not data:
break
yield data
page += 1

项目 4:实时数据流

1
2
3
4
def stream_data():
while True:
data = get_realtime_data()
yield data

项目 5:数据管道

1
2
3
def pipeline():
yield from step1(read_input())
yield from step2(process_output())

十、给 Python 生成器学习者的 4 个建议

建议 1:从 yield 开始

  • 写一个 yield 函数
  • 理解"暂停"概念

建议 2:替换大列表

  • [...] 改成 (...)
  • 立刻省内存

建议 3:用生成器读大文件

  • 文件处理是经典场景
  • 每天用

建议 4:组合生成器

  • yield from
  • 管道式处理
  • 进阶技能

十一、最后的最后

Python 生成器,3 句话总结

  1. 生成器是懒人函数:一次一个,不存所有
  2. 内存省 100 倍:处理大数据必备
  3. yield 是核心:理解 yield = 理解生成器

学 Python 6 年,我学到的最重要的事:

"不会生成器,你的 Python 是'入门级'。"

会生成器,你的代码能处理 100GB 数据

数据科学家、AI 工程师、Web 开发者都用得上**。**

学 Python 6 年,生成器是性价比最高的学习


相关阅读


科技不高冷,AI很好用。
我是晚枫,关注我,带你一起玩AI!

🎓 AI 编程实战课程

程序员晚枫专注AI编程培训,通过 《50讲 · AI编程训练营》,让小白也能用AI做出实际项目。帮你从零上手!