跳转至

生成器和普通函数有什么区别?

1️⃣ 执行方式:一次性 vs 可暂停的流

普通函数一调到底,return 之后栈帧销毁,啥也不剩。

def normal():
    return [1, 2, 3]      # 一次性返回全部结果

for i in normal():        # 先拿到完整列表,再迭代
    print(i)

生成器函数碰到 yield 会暂停,把值“吐”出去,下次 next() 或迭代再从暂停处继续。它返回的是一个生成器对象,而不是计算结果。

def gen():
    yield 1
    yield 2
    yield 3

g = gen()                 # 这里还没执行函数体!
print(next(g))            # 执行到第一个 yield,输出 1,暂停
print(next(g))            # 继续执行到第二个 yield,输出 2

▶️ 普通函数是“一次性交付”,生成器是“随用随取”。


2️⃣ 内存占用:全量装载 vs 惰性产出

这一点在处理大文件或流数据时是碾压级的。

  • 普通函数:return 一个列表,所有数据同时在内存里。读 10GB 日志,内存直接爆。

  • 生成器:每次只 yield 一行,内存里永远只保留当前行,可以处理无限流。

# 普通函数,读完整个文件才返回
def read_file(path):
    lines = []
    with open(path) as f:
        for line in f:
            lines.append(line)
    return lines

# 生成器,一行一行产出
def read_file_gen(path):
    with open(path) as f:
        for line in f:
            yield line

💡 这也是为什么很多 Python 标准库把原来返回列表的接口改成了返回生成器(如 range vs xrangemap/filter 的返回类型)。


3️⃣ 状态保存:局部变量的生命周期

普通函数每次调用,局部变量重生,彼此独立。 生成器函数的局部变量在 yield 暂停后仍然存活,下次 next() 可以接着用。

def counter():
    n = 0
    while True:
        n += 1
        yield n

c = counter()
print(next(c))  # 1
print(next(c))  # 2  ← n 的值被记住了

这个状态保持的能力,让生成器可以天然地描述“有状态的流处理管道”,而不需要你去写一个实现了 iternext 的类。


4️⃣ 双向通信:不只是产出,还能接收

生成器还有个普通函数做不到的能力:通过 send() 向暂停处传入值,这让它可以扮演轻量级协程。

def echo():
    while True:
        received = yield
        print(f"收到: {received}")

g = echo()
next(g)          # 先推到第一个 yield
g.send("hello")  # 传入值,并继续执行到下一个 yield

另外 throw()close() 可以在外部控制生成器的异常和终止,普通函数完全没有这种“外部干预执行”的能力。