深入理解 Python 生成器

一、生成器的准确定位:它不是"特殊列表",而是"惰性迭代器构造器"

生成器最准确的定义是:

生成器函数是包含 yield 的函数;调用它不会立刻执行函数体,而是返回一个生成器对象。这个对象实现了迭代器协议,可以在每次请求下一个值时继续执行,直到再次遇到 yield 或最终结束。

先看最基础的例子:

复制代码
def count_up_to(n):
    current = 1
    while current <= n:
        yield current
        current += 1

gen = count_up_to(3)

print(gen)          # <generator object count_up_to at ...>
print(next(gen))    # 1
print(next(gen))    # 2
print(next(gen))    # 3

这个例子里最重要的事实不是它输出了 1、2、3,而是下面两点:

  1. 调用 count_up_to(3) 时,函数体没有一次性执行完。
  2. 返回值不是列表,而是生成器对象。

如果把同样逻辑写成普通函数:

复制代码
def count_up_to_list(n):
    result = []
    current = 1
    while current <= n:
        result.append(current)
        current += 1
    return result

data = count_up_to_list(3)
print(data)         # [1, 2, 3]

这里的差异不是"语法不同"这么简单,而是计算模型不同:

  1. 列表方案是立即求值。
  2. 生成器方案是按需求值。
  3. 列表一次性占用完整结果的内存。
  4. 生成器只在迭代推进时产生当前值。

因此,生成器不是"另一种容器",而是一种延迟计算机制。


二、为什么 yield 会改变函数语义

普通函数的执行模型很简单:调用,执行到底,return 返回,栈帧销毁。

一旦函数体内出现 yield,这个函数就不再是普通函数,而会被编译为生成器函数。它的执行语义变成:

  1. 调用时不立即运行主体逻辑。
  2. 返回生成器对象。
  3. 每次被 next 或 for 驱动时,从上次暂停位置继续执行。
  4. 遇到 yield 暂停,并把值返回给外部。
  5. 执行完毕时抛出 StopIteration。

看一个更能说明"暂停/恢复"本质的例子:

复制代码
def demo():
    print("step 1")
    yield "A"
    print("step 2")
    yield "B"
    print("step 3")

gen = demo()

print(next(gen))
print(next(gen))
try:
    print(next(gen))
except StopIteration:
    print("generator finished")

运行顺序是:

  1. 第一次 next 时,打印 step 1,返回 A。
  2. 第二次 next 时,从上次 yield 之后继续,打印 step 2,返回 B。
  3. 第三次 next 时,继续执行,打印 step 3,然后结束并抛出 StopIteration。

这说明 yield 的核心作用不是"返回值",而是"挂起当前执行现场"。


三、生成器与迭代器的关系:生成器是迭代器的一种实现

理解生成器,不能绕开迭代器协议。Python 的迭代器协议要求对象具备两个特征:

  1. iter 方法,返回迭代器自身或另一个迭代器。
  2. next 方法,每次返回下一个值,没有值时抛出 StopIteration。

可以手写一个迭代器类:

复制代码
class CountIterator:
    def __init__(self, n):
        self.n = n
        self.current = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.current > self.n:
            raise StopIteration
        value = self.current
        self.current += 1
        return value

it = CountIterator(3)
for item in it:
    print(item)

而用生成器实现同样逻辑:

复制代码
def count_generator(n):
    current = 1
    while current <= n:
        yield current
        current += 1

for item in count_generator(3):
    print(item)

两者的语义完全一致,但生成器版本更短、更接近问题本身。这里可以得到一个非常重要的结论:

生成器不是对迭代器协议的替代,而是对迭代器协议的高级语法封装。

也正因为如此,凡是能接受迭代器的地方,通常都能接受生成器:

复制代码
total = sum(x for x in range(5))
print(total)    # 10

四、生成器对象保存了什么状态

生成器之所以能暂停后恢复,是因为它不是简单保存"上一个值",而是保存整个执行现场。包括但不限于:

  1. 局部变量当前值。
  2. 指令执行位置。
  3. 当前 try、for、while 等控制流上下文。
  4. 必要的异常处理状态。

看一个例子:

复制代码
def accumulate():
    total = 0
    for i in range(3):
        total += i
        yield total

gen = accumulate()
print(next(gen))    # 0
print(next(gen))    # 1
print(next(gen))    # 3

这里 total 会在多次恢复执行时持续存在。如果换成普通函数,函数返回后其局部状态早已消失;而生成器会在每次挂起时保留上下文。

可以把它理解成:

生成器对象是"携带执行现场的可恢复函数实例"。

这也是它比单纯"回调式逐个返回值"更强大的原因。


五、for 循环为什么天然支持生成器

很多人会用 next 手动驱动生成器,但实际工程里最常见的是 for。原因是 for 本质上就是迭代器协议的消费器。

例如:

复制代码
def squares(n):
    for i in range(n):
        yield i * i

for value in squares(5):
    print(value)

for 循环内部做的事情,本质上等价于:

复制代码
gen = squares(5)
while True:
    try:
        value = next(gen)
        print(value)
    except StopIteration:
        break

因此,生成器并不是"for 的特殊对象",而是因为它实现了迭代器协议,所以能被 for 自然消费。

这个认知非常关键,因为它意味着生成器不仅可以配合 for,也可以配合所有接受可迭代对象的标准库函数:

复制代码
print(list(squares(5)))        # [0, 1, 4, 9, 16]
print(tuple(squares(5)))       # (0, 1, 4, 9, 16)
print(max(squares(5)))         # 16

六、惰性计算是生成器最核心的工程价值

生成器的最大价值往往不是"语法优雅",而是惰性求值。所谓惰性求值,就是:

只有当调用方真正需要下一个值时,生成器才继续执行并产出该值。

看一个大数据场景:

复制代码
def read_large_file(path):
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            yield line.rstrip("\n")

for line in read_large_file("huge.log"):
    if "ERROR" in line:
        print(line)

如果这里返回列表,就意味着必须先把整个文件全部读入内存,再开始过滤。对于大文件,这会带来明显问题:

  1. 内存占用高。
  2. 首次结果出现慢。
  3. 无法做真正流式处理。

而生成器实现的是:

  1. 读一行。
  2. 处理一行。
  3. 继续下一行。

这类模式在日志处理、数据库游标消费、网络流、消息队列、爬虫管道、机器学习数据迭代中都非常常见。

再看列表方案与生成器方案的对比:

复制代码
def even_squares_list(n):
    result = []
    for i in range(n):
        if i % 2 == 0:
            result.append(i * i)
    return result

def even_squares_gen(n):
    for i in range(n):
        if i % 2 == 0:
            yield i * i

print(even_squares_list(10))
print(list(even_squares_gen(10)))

小数据下两者输出相同,但规模扩大后,生成器的内存优势会越来越明显。


七、生成器只能遍历一次,这既是特性,也是限制

生成器是一次性消费对象。遍历结束后,不能自动重置。

示例:

复制代码
def numbers():
    for i in range(3):
        yield i

gen = numbers()

print(list(gen))    # [0, 1, 2]
print(list(gen))    # []

第二次为空,不是 bug,而是因为生成器已经耗尽。

这与列表不同:

复制代码
data = [0, 1, 2]
print(list(data))   # [0, 1, 2]
print(list(data))   # [0, 1, 2]

因此,工程上要区分两个问题:

  1. 我需要的是一次性流式消费,还是可重复访问的数据集合。
  2. 我需要的是节省内存,还是多次重用结果。

如果需要多次遍历,通常有三种选择:

  1. 重新创建生成器。
  2. 先把结果物化成列表或元组。
  3. 设计成返回"生成器工厂",而不是返回单个已创建的生成器。

例如:

复制代码
def number_stream():
    for i in range(3):
        yield i

print(list(number_stream()))
print(list(number_stream()))

这才是可重复使用的形式,因为每次都重新创建了一个新生成器。


八、return 在生成器里意味着什么

普通函数中的 return 表示"返回某个结果并结束"。在生成器里,return 仍然表示结束,但它的语义更细:

  1. return 会终止生成器。
  2. 它不会像普通函数那样把值直接交给 for 循环。
  3. 如果写成 return value,这个值会附着在 StopIteration.value 上。

看例子:

复制代码
def gen():
    yield 1
    yield 2
    return "done"

g = gen()

print(next(g))
print(next(g))

try:
    next(g)
except StopIteration as e:
    print("stopped with:", e.value)

输出中,done 不会被 for 自动接收,但底层是可见的。

这说明一个重要事实:

生成器既可以"产出数据",也可以"最终返回一个结束值",只是这个结束值通常只有底层调用方才会关心。

这也正是后面理解 yield from 的关键前提。


九、send:生成器不只是产出值,也可以接收值

如果说普通迭代器是"单向流",那么生成器通过 send 获得了"双向通信"能力。

先看最小例子:

复制代码
def receiver():
    print("generator started")
    value = yield "ready"
    print("received:", value)
    yield "finished"

gen = receiver()

print(next(gen))            # 启动生成器,得到 ready
print(gen.send(42))         # 向 yield 表达式送入 42

这里需要精确理解一句话:

yield 既能把值送给外部,也能作为表达式接收外部通过 send 传回来的值。

也就是说:

复制代码
value = yield "ready"

这句分成两半理解:

  1. 先把 ready 产出给外部。
  2. 等下次恢复执行时,把 send 传入的参数赋给 value。

更完整的例子:

复制代码
def accumulator():
    total = 0
    while True:
        number = yield total
        if number is None:
            break
        total += number

gen = accumulator()

print(next(gen))        # 0
print(gen.send(10))     # 10
print(gen.send(5))      # 15

try:
    gen.send(None)
except StopIteration:
    print("accumulator stopped")

这个例子展示了生成器的另一个身份:

它不仅可以是数据源,也可以是状态机。


十、为什么第一次 send 不能直接发送非 None 值

这是生成器使用中一个非常经典的细节。下面代码会报错:

复制代码
def echo():
    value = yield
    print(value)

gen = echo()
gen.send(123)

原因是生成器刚创建时,尚未执行到第一个 yield 位置,因此没有"挂起点"可以接收外部值。必须先把它推进到第一个 yield。

正确写法:

复制代码
def echo():
    value = yield
    print("received:", value)

gen = echo()
next(gen)
try:
    gen.send(123)
except StopIteration:
    pass

所以规则是:

第一次恢复生成器时,只能用 next(gen) 或 gen.send(None)。

这是语言语义,不是实现偶然。


十一、throw:向生成器内部注入异常

生成器不仅能接收正常数据,也能从外部接收异常。这个能力由 throw 提供。

示例:

复制代码
def worker():
    try:
        yield 1
        yield 2
    except ValueError as e:
        yield f"caught: {e}"
    yield 3

gen = worker()

print(next(gen))                    # 1
print(gen.throw(ValueError("bad"))) # caught: bad
print(next(gen))                    # 3

这里发生的事情是:

  1. 生成器先产出 1。
  2. 外部不是继续 next,而是向挂起点抛入 ValueError。
  3. 生成器内部的 try/except 捕获该异常。
  4. 然后继续运行。

throw 的工程价值在于:它允许外部调度器把错误信号注入协作式执行流程中。虽然日常业务代码不常直接使用,但在早期协程框架、任务调度器、控制流库中非常重要。


十二、close:显式终止生成器

close 用于通知生成器应尽快结束。其本质是向生成器内部注入 GeneratorExit。

看例子:

复制代码
def sample():
    try:
        yield 1
        yield 2
    finally:
        print("cleaning up")

gen = sample()
print(next(gen))
gen.close()

输出会触发 finally 中的清理逻辑。

这说明生成器不仅是"会暂停的函数",还是"可管理生命周期的执行体"。当它内部持有文件句柄、网络连接、锁、事务上下文时,这一点尤其重要。

例如:

复制代码
def read_lines(path):
    f = open(path, "r", encoding="utf-8")
    try:
        for line in f:
            yield line
    finally:
        print("closing file")
        f.close()

如果外部提前停止消费,显式 close 可以让资源及时释放。


十三、yield from:真正的生成器委托机制

很多文章把 yield from 解释成"把子可迭代对象一个个 yield 出去",这只是表层现象。它更准确的定义是:

yield from 把当前生成器的控制权委托给另一个迭代器或生成器,并自动转发 next、send、throw、close,同时还能接收子生成器的最终返回值。

先看最简单的形式:

复制代码
def sub():
    yield 1
    yield 2

def main():
    yield 0
    yield from sub()
    yield 3

print(list(main()))    # [0, 1, 2, 3]

如果不用 yield from,需要手写:

复制代码
def main_manual():
    yield 0
    for item in sub():
        yield item
    yield 3

但这只是"值的转发"。yield from 的真正强大之处,在于它还能处理 return 值:

复制代码
def sub():
    yield 1
    yield 2
    return "sub finished"

def main():
    result = yield from sub()
    yield f"result was: {result}"

print(list(main()))

输出中最后一项会拿到子生成器 return 的结果。

这说明 yield from 不只是展开序列,而是在语言层面建立了一条"父生成器与子生成器之间的完整委托通道"。


十四、yield from 与手写 for 循环的本质区别

为了看清这个差异,比较两段代码。

第一种,手写 for:

复制代码
def sub():
    received = yield 1
    yield f"sub got {received}"

def outer_manual():
    for item in sub():
        yield item

第二种,yield from:

复制代码
def sub():
    received = yield 1
    yield f"sub got {received}"

def outer_delegate():
    yield from sub()

对于 send、throw、close 来说,两者行为不同。yield from 会把这些操作自动继续传递给 sub;而手写 for 只是普通地把 next 拿到的值再 yield 出来,无法完整复现委托语义。

因此,严谨地说:

yield from 不是一个简写循环,而是生成器协议的组合器。


十五、生成器表达式:语法紧凑,但语义仍然是惰性迭代

生成器表达式写法与列表推导式很像:

复制代码
squares_list = [x * x for x in range(5)]
squares_gen = (x * x for x in range(5))

print(squares_list)         # [0, 1, 4, 9, 16]
print(squares_gen)          # <generator object ...>
print(list(squares_gen))    # [0, 1, 4, 9, 16]

两者的关键差异不是括号,而是求值时机:

  1. 列表推导式会立即构造完整列表。
  2. 生成器表达式只在消费时逐项计算。

这在链式处理中很常见:

复制代码
total = sum(x * x for x in range(1_000_000))
print(total)

这里如果写成列表推导式:

复制代码
total = sum([x * x for x in range(1_000_000)])

通常就会额外创建一个庞大中间列表,而实际上 sum 并不需要一次拿到所有值。

所以一个常见实践是:

如果最终消费者是 sum、any、all、max、min、join 之外的某些支持迭代的聚合器,并且不需要中间结果复用,优先考虑生成器表达式。


十六、生成器管道:把复杂处理拆成一条数据流

生成器在工程上非常适合构建流式处理管道。每一层只做一件事,并通过 yield 向下游传递。

示例:读取日志、过滤错误、提取时间戳。

复制代码
def read_lines(lines):
    for line in lines:
        yield line.strip()

def filter_errors(lines):
    for line in lines:
        if "ERROR" in line:
            yield line

def extract_timestamps(lines):
    for line in lines:
        parts = line.split(" ", 1)
        yield parts[0]

raw_lines = [
    "2026-04-20 INFO startup",
    "2026-04-20 ERROR database down",
    "2026-04-20 ERROR timeout",
]

pipeline = extract_timestamps(filter_errors(read_lines(raw_lines)))

for item in pipeline:
    print(item)

这种风格的优势是:

  1. 每层职责单一。
  2. 中间结果不必一次性物化。
  3. 可以自然拼接。
  4. 适合超大数据流。

如果用列表中间态实现,往往会写成:

  1. 先清洗成一个列表。
  2. 再过滤成另一个列表。
  3. 再映射成第三个列表。

这样会产生更多中间对象和内存压力。


十七、生成器适合表达有限流,也适合表达无限流

生成器并不要求必须有限。它非常适合描述概念上"没有终点"的序列。

例如斐波那契无限流:

复制代码
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

gen = fibonacci()

for _ in range(10):
    print(next(gen))

普通列表无法自然表示"无限序列",但生成器可以,因为它不需要提前构造所有值。

这也说明生成器非常适合以下场景:

  1. 实时数据源。
  2. 事件流。
  3. 概念上的无限序列。
  4. 外部驱动、随取随算的计算流。

当然,这也要求消费端必须有终止条件,否则会无限运行。


十八、生成器里的异常传播机制

生成器内部出现异常时,和普通函数一样会向外传播;不同之处在于,它可能在多次恢复执行中的某一次才抛出。

示例:

复制代码
def broken():
    yield 1
    raise RuntimeError("something went wrong")

gen = broken()

print(next(gen))
try:
    print(next(gen))
except RuntimeError as e:
    print("caught:", e)

这说明生成器的错误并不是在创建对象时出现,而是在执行推进到相应位置时才暴露。

因此,在工程上要非常明确:

  1. 创建生成器对象通常不代表逻辑已成功。
  2. 真正的失败可能发生在消费阶段。
  3. 生成器 API 的调用方必须对迭代过程中的异常负责。

这个特性和数据库游标、网络流、延迟计算系统非常一致。


十九、生成器与上下文资源:不要忽视中途停止消费的问题

看一个例子:

复制代码
def data_source():
    print("open resource")
    try:
        for i in range(5):
            yield i
    finally:
        print("close resource")

gen = data_source()
print(next(gen))
print(next(gen))
gen.close()

如果外部不再继续消费,但又没有让生成器正常结束,那么内部资源可能会延迟释放。因此写生成器时,凡是涉及资源管理,都应尽量使用 try/finally 或 with。

例如:

复制代码
def read_file(path):
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            yield line

这里即便消费提前结束,只要生成器被关闭或最终被回收,with 的退出逻辑都更清晰。

原则上讲:

生成器非常适合流式资源使用,但也要求开发者更认真处理生命周期。


二十、生成器不是并发模型,但它启发了协程模型

很多现代 Python 开发者接触协程时首先想到 async 和 await,但从语言演化史看,生成器是协程的重要前身。特别是 send、throw、close、yield from 这些能力,直接铺垫了后来的协程协议。

下面这个例子不是现代推荐写法,但能帮助理解生成器为什么被视为"协作式控制流"的基础:

复制代码
def task():
    print("task step 1")
    yield
    print("task step 2")
    yield
    print("task step 3")

t = task()
next(t)
next(t)
try:
    next(t)
except StopIteration:
    pass

这个任务不是线程,也不是进程,它只是一个可被调度器反复恢复的执行体。早期很多协程库就是基于这类思想构建的。

不过必须明确:

  1. 普通生成器不是现代异步编程的完整替代。
  2. 现代异步代码应优先使用 async def 和 await。
  3. 但理解生成器,有助于更深刻地理解协程为何能够暂停和恢复。

二十一、异步生成器:生成器思想在异步世界的延伸

Python 还提供了异步生成器,用于异步上下文中的流式产出。

示例:

复制代码
import asyncio

async def async_counter():
    for i in range(3):
        await asyncio.sleep(0.1)
        yield i

async def main():
    async for item in async_counter():
        print(item)

asyncio.run(main())

异步生成器与普通生成器的关系可以概括为:

  1. 普通生成器服务于同步迭代。
  2. 异步生成器服务于异步迭代。
  3. 一个通过 for 消费。
  4. 一个通过 async for 消费。

这说明生成器背后的思想非常稳定:

无论同步还是异步,本质都是"把序列生产改造成可暂停、可恢复、按需产出的过程"。


二十二、常见误区一:把生成器当成"性能一定更高"的方案

生成器经常能减少内存占用,但它并不意味着所有场景都更快。

看例子:

复制代码
def list_version():
    return [x * 2 for x in range(1000)]

def gen_version():
    return (x * 2 for x in range(1000))

如果最终你马上就要把生成器全部转成列表:

复制代码
data = list(gen_version())

那你仍然会物化整个结果,甚至还多了一层生成器调度开销。

所以应当严谨地说:

  1. 生成器通常更省内存。
  2. 生成器不保证绝对更快。
  3. 如果最终必须完整落地全部数据,列表有时反而更直接。
  4. 生成器最适合"边生产边消费"。

二十三、常见误区二:在调试时误以为生成器"没有执行"

初学者常见疑问是:

"我明明调用了函数,为什么函数体里的打印没执行?"

例子:

复制代码
def demo():
    print("running")
    yield 1

gen = demo()
print("created")

只会打印 created,而不会打印 running。因为生成器函数被调用时只是创建生成器对象,没有推进执行。

真正执行是在:

复制代码
print(next(gen))

之后才会打印 running。

因此,调试生成器时必须分清两个时刻:

  1. 创建时刻。
  2. 消费时刻。

很多"逻辑没生效"的误判,本质上只是没有触发消费。


二十四、常见误区三:把生成器返回给多个消费者共享

因为生成器是单次消费对象,所以把同一个生成器传给多个地方时,经常会出现"有人读完了,别人就没了"的问题。

示例:

复制代码
def source():
    for i in range(5):
        yield i

gen = source()

print(next(gen))     # 0
print(list(gen))     # [1, 2, 3, 4]
print(list(gen))     # []

如果多个模块共享同一个生成器对象,就相当于共享一个会不断前进的游标。

解决方式通常是:

  1. 共享可重建的生成器工厂。
  2. 共享已经物化的数据。
  3. 明确约定只有单一消费者。

在接口设计上,这一点必须提前说明,否则非常容易引入隐蔽 bug。


二十五、生成器与列表推导式该如何选择

这个问题没有绝对答案,但有相对稳定的判断标准。

适合使用列表的场景

  1. 数据量不大。
  2. 需要多次遍历。
  3. 需要随机访问或切片。
  4. 需要立即看到完整结果。

示例:

复制代码
values = [x * x for x in range(10)]
print(values[3])
print(values[-1])

适合使用生成器的场景

  1. 数据量大或可能无限。
  2. 只需要单次流式消费。
  3. 处理过程适合流水线。
  4. 希望尽快得到首批结果,而不是等待全部完成。

示例:

复制代码
values = (x * x for x in range(10_000_000))
print(next(values))
print(next(values))

所以真正的选择依据不是"哪种语法更高级",而是数据访问模式。


二十六、工程上如何写出可维护的生成器

生成器很强,但也容易被写成难调试的"隐式控制流"。比较稳妥的实践包括:

1. 让每个生成器职责单一

复制代码
def filter_positive(numbers):
    for n in numbers:
        if n > 0:
            yield n

这种风格比一个生成器内部既过滤、又映射、又统计、又记录日志更容易维护。

2. 明确说明是否一次性消费

复制代码
def load_records():
    for i in range(3):
        yield {"id": i}

调用方需要知道这是流,而不是静态集合。

3. 涉及资源时始终考虑 finally 或 with

复制代码
def open_and_stream(path):
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            yield line

4. 不要为"炫技"而滥用 send 和 throw

这些机制很强,但会显著增加认知负担。大多数业务代码只需要普通 yield 即可。

5. 对外接口尽量稳定

如果某个函数对调用方来说更像"数据集合",而不是"数据流",那么直接返回列表可能更符合语义。


二十七、一个完整案例:用生成器构建日志处理流水线

下面给出一个稍完整的工程化例子,展示生成器如何组合出清晰的数据处理管道。

复制代码
def read_lines(lines):
    for line in lines:
        yield line.strip()

def parse_log(lines):
    for line in lines:
        parts = line.split(" ", 2)
        if len(parts) == 3:
            date, level, message = parts
            yield {
                "date": date,
                "level": level,
                "message": message,
            }

def filter_level(records, target_level):
    for record in records:
        if record["level"] == target_level:
            yield record

def format_messages(records):
    for record in records:
        yield f'{record["date"]}: {record["message"]}'

raw_data = [
    "2026-04-20 INFO startup complete",
    "2026-04-20 ERROR database unavailable",
    "2026-04-20 WARNING retrying",
    "2026-04-20 ERROR timeout happened",
]

pipeline = format_messages(
    filter_level(
        parse_log(
            read_lines(raw_data)
        ),
        "ERROR"
    )
)

for item in pipeline:
    print(item)

这个例子体现出生成器在工程中的几个核心价值:

  1. 每一层输入输出都统一为可迭代流。
  2. 数据处理过程自然分层。
  3. 中间结果无需全部落地。
  4. 上游和下游耦合度低。

这就是生成器真正擅长的场景:面向流,而不是面向块。


二十八、从语言设计角度再看生成器

如果从更抽象的角度总结,生成器解决的是一个经典问题:

如何把"一个会逐步产生结果的过程",表达成"一个可被统一消费的对象"。

传统函数更适合表达"输入一次,输出一次"的映射关系;而现实世界里大量问题并不是这样:

  1. 文件是一行一行读的。
  2. 网络响应是一块一块到的。
  3. 日志是一条一条处理的。
  4. 事件是一件一件发生的。
  5. 无限序列理论上没有完整终点。

生成器的优雅之处在于,它没有发明一套完全陌生的新模型,而是在普通函数基础上,只通过 yield 就把"过程"改造成了"可恢复的迭代器"。

这也是为什么生成器一直是 Python 最具代表性的语言特性之一。


结论

生成器的本质,不是"更省内存的列表替代品",而是"把逐步计算过程对象化、迭代化、惰性化"的语言机制。理解生成器,应当把握以下几个层次:

  1. 语法层面:函数中出现 yield,就会变成生成器函数。
  2. 对象层面:调用生成器函数得到的是生成器对象,它实现了迭代器协议。
  3. 执行层面:生成器可以暂停和恢复,保存完整执行现场。
  4. 通信层面:除了 next 取值,还可以用 send、throw、close 进行控制。
  5. 组合层面:yield from 提供完整委托机制。
  6. 工程层面:生成器最适合流式处理、大数据迭代、无限序列和处理管道。
  7. 边界层面:它是一次性消费对象,不适合所有场景,也不天然保证更快。

如果只记一句话,最值得记住的是:

生成器让"计算结果"不再必须一次性存在,而可以作为"按需推进的过程"被消费。

相关推荐
青 春 记 忆6 小时前
Dify Docker Compose 通用无损升级指南:从备份、双版本预演到切换与回滚
运维·人工智能·python·docker·容器
GlueNa2SiO36 小时前
03-Flask模板引擎Jinja2详解
笔记·python·flask
临沂GEO9 小时前
GEO搜索优化科普|正规地理位置流量运营入门指南
大数据·人工智能·python·流量运营
大模型码小白9 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
荷蒲10 小时前
【小白量化Qbuddy】利用AI学习中文Python
人工智能·python·学习
数据狐(Datafox)12 小时前
京东商品列表API技术解析与落地应用(含标准 JSON 示例)
java·大数据·前端·人工智能·python·数据分析·json
量化吞吐机12 小时前
2026年下半年手工交易规则走向量化表达,产品该先接住哪一步
人工智能·python
Metaphor69213 小时前
使用 Python 读取和删除 Excel 文件属性
python·excel
迷迭香yy13 小时前
Python构建转融券多空识别系统从接口到因子的全流程 IG50免费开源股票数据API接口
开发语言·python·开源
月光船幽幽14 小时前
真实即粗糙,光滑是伪造
人工智能·python