Published on

理解 Python 生成器

Authors

在参加 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数据分页
  • 无限序列

❌ 不适合用生成器的场景:

  • 需要多次遍历数据
  • 需要随机访问数据
  • 数据量很小

💡 面试小技巧:回答生成器相关问题时,最好能结合实际场景,而不是简单地背概念!

延伸阅读