当前位置:首页>python>【一起学 Python】第 66 天:生成器函数与 yield 关键字

【一起学 Python】第 66 天:生成器函数与 yield 关键字

  • 2026-09-02 19:01:19
【一起学 Python】第 66 天:生成器函数与 yield 关键字

欢迎来到 Python 学习计划的第 66 天!🎉

昨天我们深入学习了 迭代器协议(__iter__ 与 __next__),知道了如何自定义迭代器类。但每次都要写一个类来实现迭代器太繁琐了!

今天我们将学习 Python 提供的更简洁的方式——生成器(Generator)。只需一个函数和一个 yield 关键字,就能轻松创建迭代器。这是理解异步编程(Asyncio)的前置知识!

一、什么是生成器?

1. 核心定义

生成器 是一种特殊的迭代器,通过函数和 yield 关键字创建。与普通函数不同,生成器函数不会一次性返回所有结果,而是在每次调用时产出一个值,然后暂停执行,等待下次调用。

2. 生成器函数 vs 普通函数

特性

普通函数

生成器函数

关键字

return

yield

返回值

直接返回结果

返回生成器对象

执行方式

一次性执行完

分步执行,可暂停

状态保存

不保存局部变量

保存局部变量状态

内存占用

可能很大(列表)

按需生成,很小

# 普通函数:立即计算所有值def get_numbers():    result = []    for i in range(5):        result.append(i)    return result  # 返回 [0, 1, 2, 3, 4]# 生成器函数:按需生成def generate_numbers():    for i in range(5):        yield i  # 返回生成器对象print(get_numbers())       # [0, 1, 2, 3, 4]print(generate_numbers())  # <generator object ...>

二、yield 关键字详解

1. yield 的作用

  • 产出一个值:类似 return,但不会终止函数。
  • 暂停执行:函数状态被冻结,局部变量保留。
  • 等待唤醒:下次调用 next() 时从暂停处继续执行。

2. 执行流程示例

def demo_generator():    print("第一步")    yield 1    print("第二步")    yield 2    print("第三步")    yield 3    print("结束")gen = demo_generator()# 此时函数还未执行print("调用 next()")print(next(gen))  # 输出:第一步 \n 1print("再次调用 next()")print(next(gen))  # 输出:第二步 \n 2

3. 自动处理 StopIteration

当函数执行完毕(遇到 return 或自然结束),生成器会自动抛出 StopIteration 异常,for 循环会自动捕获。

def countdown(n):    while n > 0:        yield n        n -= 1for num in countdown(3):    print(num)  # 3, 2, 1 (自动停止)

三、生成器表达式

类似列表推导式,但使用圆括号。这是创建简单生成器的最便捷方式。

# 列表推导式 - 立即创建所有元素squares_list = [x**2 for x in range(10)]# 生成器表达式 - 按需生成squares_gen = (x**2 for x in range(10))print(type(squares_list))  # <class 'list'>print(type(squares_gen))   # <class 'generator'># 遍历生成器for sq in squares_gen:    print(sq)
💡 优势:生成器表达式内存占用极小,适合大数据处理。

四、高级功能:send(), throw(), close()

生成器不仅是迭代器,还可以作为协程使用,支持双向通信。

1. send() 方法

向生成器发送值,该值成为 yield 表达式的结果。

def accumulator():    total = 0    while True:        value = yield total  # 接收 send 发送的值        if value is not None:            total += valuegen = accumulator()print(next(gen))      # 0 (启动生成器,必须先用 next 或 send(None))print(gen.send(10))   # 10 (total = 0 + 10)print(gen.send(20))   # 30 (total = 10 + 20)

2. throw() 和 close()

在生成器内部抛出异常或关闭生成器。

def controlled_generator():    try:        while True:            yield "运行中"    except ValueError:        yield "捕获到 ValueError"    finally:        print("清理资源")gen = controlled_generator()print(next(gen))        # 运行中print(gen.throw(ValueError))  # 捕获到 ValueErrorgen.close()             # 清理资源

3. yield from

委托给另一个生成器,简化嵌套循环。

def sub_generator():    yield 1    yield 2def main_generator():    yield "开始"    yield from sub_generator()  # 委托    yield "结束"for item in main_generator():    print(item)  # 开始,1, 2, 结束

五、OOP 实战应用

1. 类中的生成器方法

class DataProcessor:    def __init__(self, data):        self.data = data  # 实例属性    def filter_even(self):        """生成器方法:过滤偶数"""        for item in self.data:            if item % 2 == 0:                yield item    def transform(self):        """生成器方法:转换数据"""        for item in self.filter_even():  # 链式调用生成器            yield item * 10# 使用processor = DataProcessor([1, 2, 3, 4, 5])for value in processor.transform():    print(value)  # 20, 40

2. 树的遍历

class TreeNode:    def __init__(self, value, children=None):        self.value = value        self.children = children or []    def traverse(self):        """深度优先遍历生成器"""        yield self.value        for child in self.children:            yield from child.traverse()  # 递归委托# 构建树tree = TreeNode(1, [    TreeNode(2, [TreeNode(4), TreeNode(5)]),    TreeNode(3)])# 遍历for value in tree.traverse():    print(value)  # 1, 2, 4, 5, 3

3. 无限序列

class IdGenerator:    """ID 生成器类"""    _counter = 0  # 类属性:共享计数器    @classmethod    def generate_ids(cls, start=0):        """类方法生成器:生成无限 ID"""        current = start        while True:            yield current            current += 1# 使用id_gen = IdGenerator.generate_ids(1000)print(next(id_gen))  # 1000print(next(id_gen))  # 1001

六、实际应用示例

1. 读取大文件

避免一次性加载整个文件到内存。

def read_large_file(filepath, chunk_size=1024):    with open(filepath, "r", encoding="utf-8") as f:        while True:            chunk = f.read(chunk_size)            if not chunk:                break            yield chunk# 即使文件有 10GB,内存占用也很小for chunk in read_large_file("huge_log.txt"):    if "ERROR" in chunk:        print(chunk)

2. 数据管道(Pipeline)

构建流式数据处理链,每个步骤都是惰性的。

def read_data():    for i in range(100):        yield {"id": i, "value": i * 10}def filter_data(data):    for item in data:        if item["value"] > 500:            yield itemdef transform_data(data):    for item in data:        yield {**item, "doubled": item["value"] * 2}# 构建管道 - 数据流式通过,不在中间积累pipeline = transform_data(filter_data(read_data()))for record in pipeline:    print(record)  # 只在遍历时才真正执行计算

3. 斐波那契数列

生成无限序列,按需取值。

def fibonacci():    a, b = 0, 1    while True:        yield a        a, b = b, a + b# 获取前 10 个fib = fibonacci()for _ in range(10):    print(next(fib), end=" ")  # 0 1 1 2 3 5 8 13 21 34

七、常见误区与注意事项

1. 一次性消费

生成器只能迭代一次,耗尽后需要重新创建。

gen = (x for x in range(3))print(list(gen))  # [0, 1, 2]print(list(gen))  # [] (已耗尽)# 正确:重新创建gen = (x for x in range(3))

2. 无法获取长度

生成器不支持 len(),也无法随机访问。

gen = (x for x in range(100))# print(len(gen))  # TypeError# print(gen[5])    # TypeError

3. send() 启动

第一次调用必须用 next() 或 send(None) 启动生成器。

def coro():    x = yield    print(x)c = coro()# c.send(10)  # ❌ TypeError: 需要先用 next 启动next(c)       # ✅ 启动c.send(10)    # ✅ 发送值

4. 调试困难

无法直接打印所有内容,转为列表会耗尽生成器。

# 调试技巧:仅用于小数据gen = (x for x in range(5))print(list(gen))  # 查看内容,但生成器已耗尽

八、总结

知识点

说明

生成器

使用 yield 的函数,返回迭代器对象

yield

产出值并暂停,保存局部状态

表达式

(x for x in ...) 简洁创建生成器

send()

向生成器发送值,实现双向通信

yield from

委托给子生成器,简化嵌套

一次性

生成器只能迭代一次,无法重置

内存

惰性计算,内存占用固定且很小

核心要点

  1. 生成器是特殊的迭代器,自动实现 __iter__ 和 __next__。
  2. yield 暂停函数,下次调用从暂停处继续。
  3. 生成器表达式 比列表推导式更省内存。
  4. send() 方法 让生成器可作为协程使用。
  5. 注意一次性消费,耗尽后需重新创建。

📌 明日预告:生成器的优势:惰性计算与内存节省

明天我们将进入 并发与迭代模块第三天!

  • 主题:生成器的优势:惰性计算与内存节省
  • 核心问题:
    1. 什么是惰性计算(Lazy Evaluation)?
    2. 生成器比列表节省多少内存?
    3. 如何处理无限序列?
    4. 数据管道如何优化内存?
    5. 何时使用生成器,何时使用列表?

💡 提前思考:

  1. 如果要处理 10GB 的日志文件,用列表会发生什么?
  2. 生成器表达式 sum(x**2 for x in range(1000000)) 为什么比列表快?
  3. 惰性计算对性能有什么影响?

掌握生成器函数,是迈向高效 Python 编程的关键一步!明天深入理解内存优化!继续加油!🚀

最新文章

随机文章