科普:Python 中, `return`与`yield` (及<generator object fibonacci at 0x.........>)
看如下程序:
# 定义一个生成斐波那契数列的生成器函数
def fibonacci():a, b = 0, 1while True: # 1、死循环?yield aa, b = b, a + b# 调用生成器函数 → 得到生成器对象(而非直接返回数列值)
gen = fibonacci()
print(gen) # 2、输出是什么?
程序中我打了两个问号:1、死循环?2、输出是什么?
对于第2个问号(2、输出是什么?),一运行便知:<generator object fibonacci at 0x0000017FF0903850>(地址可能不同)。即:这里直接打印 gen
就会显示生成器对象的信息,而不是斐波那契数列的具体值——这是生成器“惰性计算”的特性决定的(不会提前生成数据,而是生成生成器的对象,然后由生成器的对象按需生成数据)。
为回答第一个问号(),我们比较return
与yield
两关键字。
在 Python 中,y ield
和 return
都是用于在函数中返回值的关键字,但它们的行为和适用场景有本质区别,核心差异在于是否终止函数执行以及返回值的方式。
1. 基本功能与函数类型
return
:用于普通函数,作用是返回一个值并立即终止函数,函数的局部状态(如变量、执行位置)会被销毁。yield
:用于生成器函数(包含yield
的函数),作用是返回一个值并暂停函数执行,同时保存函数当前的状态(局部变量、执行位置等),下次调用时从暂停处继续执行。
2. 执行流程对比
(1)return
的执行流程
def return_func():print("步骤1")return 1 # 返回值并终止函数print("步骤2") # 永远不会执行result = return_func()
print(result) # 输出:步骤1 → 1
- 函数执行到
return
时,立即返回值并结束,后续代码不再执行。 - 再次调用函数时,会重新从头开始执行。
(2)yield
的执行流程
def yield_func():print("步骤1")yield 1 # 返回值并暂停print("步骤2")yield 2 # 返回值并暂停print("步骤3")# 调用生成器函数,返回生成器对象(不执行函数体)
gen = yield_func()# 第一次触发:执行到第一个yield,返回1并暂停
print(next(gen)) # 输出:步骤1 → 1# 第二次触发:从暂停处继续,执行到第二个yield,返回2并暂停
print(next(gen)) # 输出:步骤2 → 2# 第三次触发:从暂停处继续,执行完剩余代码,无更多yield,抛出StopIteration
print(next(gen)) # 输出:步骤3 → 报错(StopIteration)
- 生成器函数被调用时,不会立即执行,而是返回一个生成器对象。
- 每次通过
next(生成器)
或迭代触发时,函数执行到下一个yield
处暂停,返回值并保存状态。 - 直到函数执行完毕(无更多
yield
),再次调用会抛出StopIteration
异常。
3. 返回结果的差异
-
return
:直接返回一个具体的值(或None
),调用函数后立即得到结果。def add(a, b):return a + bprint(add(2, 3)) # 直接得到结果:5
-
yield
:返回的是一个生成器对象(一种特殊的迭代器),而非直接返回值。需要通过迭代(如for
循环)或next()
方法逐步获取值。def generate_numbers(n):for i in range(n):yield i# 生成器对象本身不是结果,需要迭代获取 gen = generate_numbers(3) print(next(gen)) # 0 print(next(gen)) # 1
4. 内存与适用场景
-
return
:适合返回有限且少量的数据,因为它会一次性将所有结果加载到内存中(例如返回一个列表)。def get_list(n):return [i for i in range(n)] # 一次性生成所有数据,占用内存print(get_list(5)) # [0, 1, 2, 3, 4]
-
yield
:适合返回大量数据、无限序列,或需要按需生成数据的场景(节省内存)。因为它不会一次性生成所有数据,而是“按需产出”。def infinite_numbers():i = 0while True:yield i # 无限生成数据,不占用大量内存i += 1# 取前5个值,无需生成所有数据 gen = infinite_numbers() for _ in range(5):print(next(gen), end=" ") # 0 1 2 3 4
这样功能就回答了文章开头提出的第一个问题(1、死循环?):它并是不去执行死循环,而是根据需要执行“有限”步骤。
5. 其他关键区别
-
在生成器中使用
return
:生成器函数中也可以用return
,但它不会返回值(会被忽略),而是直接触发StopIteration
异常,终止生成器。def gen_with_return():yield 1return # 触发StopIterationyield 2 # 不会执行gen = gen_with_return() print(next(gen)) # 1 print(next(gen)) # 报错:StopIteration
-
yield
不能在普通函数中使用:如果普通函数(无yield
)中使用yield
,会报错;反之,生成器函数中可以没有return
(默认执行到末尾触发StopIteration
)。
特性 | return | yield |
---|---|---|
函数类型 | 普通函数 | 生成器函数 |
执行效果 | 返回值并终止函数 | 返回值并暂停函数(保存状态) |
返回结果 | 直接返回具体值 | 返回生成器对象(需迭代取值) |
内存占用 | 一次性加载所有结果,占用内存较多 | 按需生成,内存占用低 |
适用场景 | 返回有限/少量数据,立即获取结果 | 大量数据、无限序列、按需生成 |
简单说:return
是“一去不返”(终止函数),yield
是“藕断丝连”(暂停并可恢复)。