卡码笔记-最强八股文
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
  • 本栏必读

    • Python面试题专栏介绍
    • Python学习路线
    • Python零基础入门课
  • 基础语法

  • 函数、作用域与模块

  • 面向对象

  • 进阶语法与语言机制

    • 可迭代对象和迭代器有什么区别?
    • 生成器是什么?yield为什么能节省内存?
      • 简要回答
      • 详细回答
      • 知识拓展
    • Python装饰器的原理是什么?
    • Python上下文管理器是什么?with如何自动释放资源?
    • Python异常体系是什么?try、except、else和finally怎么执行?
    • Python浅拷贝和深拷贝有什么区别?
    • Python类型标注有什么用?常见类型应该怎么写?
    • dataclass、namedtuple和普通类有什么区别?
    • Python如何管理内存?引用计数和垃圾回收是什么?

# 生成器是什么?yield为什么能节省内存?

面试官问:"Python中的生成器是什么?yield为什么能节省内存?"

很多人会答"生成器就是用yield的函数",但这只是表面现象。真正要讲清楚的是:生成器是一种特殊的迭代器,通过yield暂停和恢复函数执行,实现惰性求值,只在需要时才计算下一个值。

# 简要回答

生成器(Generator) 是一种特殊的迭代器,可以通过两种方式创建:

  1. 生成器函数:包含yield关键字的函数。调用时不执行函数体,而是返回一个生成器对象。
  2. 生成器表达式:类似列表推导式,但使用圆括号()。

yield的工作机制:函数执行到yield时暂停并返回值,保存当前状态;下次调用next()时从暂停位置继续执行,直到遇到下一个yield或函数结束。

为什么节省内存:生成器采用惰性求值,只在需要时计算一个值并立即返回,不会一次性把所有结果存入内存。处理大数据集时,内存占用可以从O(n)降到O(1)。

# 详细回答

# 生成器函数的基本用法

普通函数使用return返回后结束,生成器函数使用yield返回后暂停:

def count_up(n):
    i = 0
    while i < n:
        yield i
        i += 1

gen = count_up(3)
print(next(gen))  # 0
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # StopIteration
1
2
3
4
5
6
7
8
9
10
11

调用count_up(3)不会立即执行函数体,而是返回一个生成器对象。每次调用next()时,函数从上次暂停的位置继续执行,直到遇到下一个yield。

# yield如何暂停和恢复

生成器函数的执行状态保存在生成器对象中,包括局部变量、指令指针和调用栈:

def fibonacci(n):
    a, b = 0, 1
    count = 0
    while count < n:
        yield a
        a, b = b, a + b
        count += 1

fib = fibonacci(5)
print(next(fib))  # 0, a=0, b=1保存
print(next(fib))  # 1, a=1, b=1保存
print(next(fib))  # 1, a=1, b=2保存
1
2
3
4
5
6
7
8
9
10
11
12

每次yield后,变量a、b、count的值都会保存在生成器对象内部。下次调用next()时,从yield的下一行继续执行。

生成器状态暂停与恢复

# 惰性求值如何节省内存

对比列表和生成器处理100万个数字:

# 列表推导式: 一次性创建100万个元素
numbers_list = [x * 2 for x in range(1000000)]
# 内存占用约 8MB (每个整数约8字节)

# 生成器表达式: 只保存当前计算状态
numbers_gen = (x * 2 for x in range(1000000))
# 内存占用约 几百字节 (只保存生成器对象和状态)
1
2
3
4
5
6
7

使用生成器时:

numbers_gen = (x * 2 for x in range(1000000))
for num in numbers_gen:
    print(num)
    if num > 100:
        break  # 只计算了前51个,后面的不会计算
1
2
3
4
5

生成器只在for循环需要下一个值时才计算一次,计算完立即返回。循环提前结束时,后续的值永远不会被计算。

# 生成器表达式 vs 列表推导式

语法只差一个括号,但行为完全不同:

# 列表推导式: 立即计算所有值
squares_list = [x**2 for x in range(5)]
print(squares_list)  # [0, 1, 4, 9, 16]

# 生成器表达式: 返回生成器对象
squares_gen = (x**2 for x in range(5))
print(squares_gen)   # <generator object>
print(list(squares_gen))  # [0, 1, 4, 9, 16]
1
2
3
4
5
6
7
8

如果只需要遍历一次,生成器表达式更高效;如果需要多次访问或随机访问,必须使用列表。

# 生成器是一次性的

生成器是迭代器,消耗后不会重置:

gen = (x for x in range(3))
print(list(gen))  # [0, 1, 2]
print(list(gen))  # [] 已经消耗完
1
2
3

如果需要多次遍历,必须重新创建生成器或改用列表。

# yield from委托子生成器

yield from可以把生成器委托给另一个可迭代对象:

def sub_gen():
    yield 1
    yield 2

def main_gen():
    yield from sub_gen()
    yield 3

gen = main_gen()
print(list(gen))  # [1, 2, 3]
1
2
3
4
5
6
7
8
9
10

等价于:

def main_gen():
    for value in sub_gen():
        yield value
    yield 3
1
2
3
4

yield from简化了嵌套生成器的写法,在递归生成器中很常用。

# 知识拓展

Q1: 生成器和迭代器有什么区别?

生成器是迭代器的一种实现方式。迭代器可以通过类实现__iter__()和__next__()方法,也可以通过生成器函数实现。生成器的优势是写法简洁,不需要手动管理状态:

# 迭代器写法
class Counter:
    def __init__(self, n):
        self.n = n
        self.i = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.i >= self.n:
            raise StopIteration
        self.i += 1
        return self.i

# 生成器写法
def counter(n):
    i = 0
    while i < n:
        i += 1
        yield i
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Q2: 什么时候应该用生成器?

  • 处理大数据集或无限序列(日志流、传感器数据)。
  • 只需要遍历一次,不需要随机访问或多次遍历。
  • 需要流式处理,边产生边消费(数据管道)。
  • 递归或状态机场景,用yield比用类管理状态更清晰。

Q3: 生成器一定比列表快吗?

不一定。生成器节省内存,但不一定更快:

  • 如果需要多次遍历,生成器需要重新计算,列表只需一次计算。
  • 如果数据量小,列表的访问速度更快(连续内存,CPU缓存友好)。
  • 如果计算逻辑复杂,生成器每次调用都要执行一遍。

生成器的优势是空间,不是时间。

Q4: 为什么生成器函数调用后不立即执行?

如果立即执行,函数会运行到第一个yield或结束,无法实现惰性求值。延迟执行让生成器在需要时才开始计算,这是惰性求值的前提。

Q5: 可以用return结束生成器吗?

可以。return会结束生成器并抛出StopIteration,返回值会作为异常的value属性:

def gen():
    yield 1
    return "done"
    yield 2  # 永远不会执行

g = gen()
print(next(g))  # 1
try:
    next(g)
except StopIteration as e:
    print(e.value)  # done
1
2
3
4
5
6
7
8
9
10
11

普通for循环会忽略返回值,但在生成器委托(yield from)中可以捕获。

Last Updated: 10/13/2026, 5:57:00 PM

← 可迭代对象和迭代器有什么区别? Python装饰器的原理是什么? →

评论

验证登录状态...

侧边栏 侧边栏
夜间模式 夜间
卡码简历 卡码简历
代码随想录 代码随想录
卡码投递表 卡码投递表🔥
2026实习校招群 2026群
添加客服微信 2026实习校招客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记 支持卡码笔记
鼓励/支持/赞赏Carl 卡码笔记赞赏码
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪