- Published on
理解 Python 生成器
- Authors
- Name
- 阮达达
- Github
- @ruandada
在参加 Python 面试时,通常会遇到面试官提问:
请问你了解 Python 生成器吗?"
"呃...好像是为了节省内存...
这个回答并不算错,但未免太过简单。生成器确实能帮我们节省内存,但它的强大之处远不止于此。本文将带你深入理解 Python 生成器的本质,让你不仅能在面试中侃侃而谈,更能在实际工作中熟练运用这个强大的特性。
从一个实际问题说起
想象你正在开发一个网站的日志分析系统。某天,你的同事小王慌慌张张地跑来:
"不好了!生产服务器内存爆了!"
你查看代码,发现是这样的:
def analyze_logs(log_file):
# 一次性读取所有日志到内存
logs = [line for line in open(log_file)]
for log in logs:
if 'ERROR' in log:
send_alert(log)
这段代码的问题在哪?没错,它试图一次性将整个日志文件读入内存。当日志文件达到几个 GB 时,服务器内存就吃不消了。
生成器:优雅的解决方案
什么是生成器?
生成器就像是一个会下魔术的函数 —— 它可以在需要的时候变出数据,用完即焚,不占地方。
让我们改造上面的代码:
def analyze_logs(log_file):
with open(log_file) as f:
for line in f: # 文件对象本身就是一个生成器!
if 'ERROR' in line:
yield line.strip()
# 优雅地处理大文件
for error_log in analyze_logs('huge.log'):
send_alert(error_log)
🎯 小贴士:Python 的文件对象本身就是一个生成器,它不会一次性读取整个文件!
生成器 vs 迭代器:到底哪里不一样?
想象你在点外卖:
- 迭代器就像是一次性把所有菜都做好,打包送到你手上
- 生成器则像是有一位大厨在你家,你想吃什么现做什么
来看代码对比:
# 迭代器:需要自己管理"厨房"的状态
class DishIterator:
def __init__(self, dishes):
self.dishes = dishes
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.dishes):
raise StopIteration
dish = self.dishes[self.index]
self.index += 1
return f"做好了一个{dish}"
# 生成器:厨师自己就知道做到哪了
def dish_generator(dishes):
for dish in dishes:
yield f"做好了一个{dish}"
# 使用对比
dishes = ['煎饼', '炒面', '炒饭']
for dish in dish_generator(dishes):
print(dish) # 现点现做,新鲜!
生成器的实战应用
1. 大数据流处理
假设你需要处理一个超大的股票交易数据文件:
def process_trades(file_path):
for line in open(file_path):
trade = json.loads(line)
if trade['amount'] > 1000000: # 只关注大额交易
yield trade
# 使用生成器处理大文件
for big_trade in process_trades('trades.json'):
notify_risk_team(big_trade)
2. 无限数据流
生成器特别适合处理无限数据流,比如实时股票价格:
def price_stream():
"""模拟实时股票价格流"""
while True:
price = get_latest_price() # 假设这是个外部API
yield price
time.sleep(1) # 每秒更新一次
# 使用生成器处理实时数据
for price in price_stream():
if price > threshold:
send_alert()
趣味实验:内存使用对比
让我们做个有趣的实验,对比列表和生成器的内存使用:
import sys
import memory_profiler
@memory_profiler.profile
def test_list():
return [i * i for i in range(1000000)]
@memory_profiler.profile
def test_generator():
return (i * i for i in range(1000000))
# 运行测试
list_result = test_list()
gen_result = test_generator()
运行这段代码,你会发现有趣的结果:
- 列表版本:占用了几十MB的内存
- 生成器版本:只占用几KB!
小结:什么时候用生成器?
✅ 使用生成器的最佳场景:
- 处理大文件
- 实时数据流
- API数据分页
- 无限序列
❌ 不适合用生成器的场景:
- 需要多次遍历数据
- 需要随机访问数据
- 数据量很小
💡 面试小技巧:回答生成器相关问题时,最好能结合实际场景,而不是简单地背概念!