在同一个函数中返回和产生输出的概念

100

当在Python函数中同时使用yield和return时,会发生什么?

def find_all(a_str, sub):
    start = 0
    while True:
        start = a_str.find(sub, start)
        if start == -1: return
        yield start
        start += len(sub) # use start += 1 to find overlapping matches

它仍然是一个发电机吗?

4个回答

99

是的,它仍然是一个生成器。 return 与引发 StopIteration (几乎)等效。

PEP 255 对此进行了解释:

规范:Return

生成器函数也可以包含以下形式的 return 语句:

"return"

注意,在生成器的主体中,return语句不允许使用expression_list(尽管当然可以在嵌套在生成器内的非生成器函数的主体中出现)。

当遇到return语句时,控制流程与任何函数返回一样,执行适当的finally子句(如果存在)。然后会引发StopIteration异常,表示迭代器已耗尽。如果在没有明确的返回的情况下超出生成器的末尾,也会引发StopIteration异常。

请注意,对于生成器函数和非生成器函数,return意味着“我已经完成,并且没有有趣的返回内容”。

请注意,return并不总是等同于引发StopIteration:差异在于如何处理包含的try/except结构。例如:

>>> def f1():
...     try:
...         return
...     except:
...        yield 1
>>> print list(f1())
[]

因为,就像在任何函数中一样,return语句仅仅是退出函数的作用,但是

>>> def f2():
...     try:
...         raise StopIteration
...     except:
...         yield 42
>>> print list(f2())
[42]

因为StopIteration被一个裸露的"except"捕获,而任何异常也是如此。


11
你知道如果return有一个参数会发生什么吗? - zwol
21
在Python 2.x中,这将会导致语法错误:SyntaxError: 'return' with argument inside generator。在Python 3.x中允许使用,但主要用于协程 - 你可以通过使用yield coroutine()(或者根据所使用的异步框架,使用yield from coroutine())对其他协程进行异步调用,并且可以使用 return value 从协程中返回任何想要返回的值。在Python 2.x中,你需要使用类似于raise Return(value)的技巧来从协程中返回值。 - dano

43

是的,它仍然是一个生成器。可以使用空的returnreturn None来结束一个生成器函数。这相当于引发StopIteration(详见@NPE的答案)。

请注意,在Python 3.3之前的版本中,带有非None参数的return会导致SyntaxError

正如@BrenBarn在评论中指出的那样,从Python 3.3开始,返回值现在被传递给StopIteration

来自PEP 380

在一个生成器中,语句

return value

语义上等同于

raise StopIteration(value)

2
你知道如果return有一个参数(除了None)会发生什么吗? - zwol
9
在Python 3.3及以上版本中,你可以使用带有参数的return语句将参数传递给引发StopIteration异常的生成器。有关详细信息,请参见此问题。 - BrenBarn
1
@AshwiniChaudhary 新的 asyncio 模块中的协程实现是基于该特性(以及 yield from 关键字)构建的。 - dano
4
@AshwiniChaudhary 这使得Python支持了基本的协程功能,即通过value = yield等方式将值/异常发送到生成器中并从中接收。引入yield from和从生成器中return值的能力随着PEP 380的出现,这两个特性都被asyncio所利用。如果只使用PEP 343提供的功能,仍然可以拥有强大的协程实现,只是编写起来可能不太简洁。 - dano
值得明确指出的是,在for循环中它将会默默忽略返回的值,这可能会相当违反直觉。 - Bernhard Barker
显示剩余2条评论

17

有一种方法可以在函数中实现同时具有yield和return功能,允许你返回一个值或生成器。

可能它不像你想的那么简洁,但它确实能够实现你期望的功能。

下面是一个例子:

def six(how_many=None):
    if how_many is None or how_many < 1:
        return None  # returns value

    if how_many == 1:
        return 6  # returns value

    def iter_func():
        for count in range(how_many):
            yield 6
    return iter_func()  # returns generator

不清楚“它确实做了你期望的事情”。您能否提供一个示例,说明您的方法如何得到良好的应用? - ShpielMeister
“that it does do what you expect” 的意思是你期望的功能确实被实现了。就像“在同一个函数中使用Return和Yield”这个问题一样。个人而言,我已经转向Haskell语言,可以很好地使用代数数据类型来管理它,但是在Python中,即使是管理类型也很困难,因此它不适用于Python类型声明。所以,如果你问如何将其应用到实际中,请不要使用它。否则,这允许你返回零个、一个或多个值。这可以用于有效地遍历树形结构。 - William Rusnack

1
注意:在下面的示例中,您不会收到StopIteration异常。
def odd(max):
    n = 0
    while n < max:
        yield n
        n = n + 1
    return 'done'


for x in odd(3):
    print(x)

for循环会捕获它。这是它停止的信号。

但你可以用以下方式捕获它:

g = odd(3)

while True:
    try:
        x = next(g)
        print(x)
    except StopIteration as e:
        print("g return value:", e.value)
        break

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接