python生成器的yield/return

yield/return from python generators

在没有帮助的情况下,我遇到了一种难以理解的行为。这是一个递归函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
OPERATORS = ['+', '-', '*', '/']

def recursive(value, operands):
    if not operands:
        return value
    for operator in OPERATORS:
        new_value = str(eval(value + operator + operands[-1]))
        new_operands = operands[:-1]
        yield from recursive(new_value, new_operands)

def countdown(value, operands):
    return next(i for i in recursive(value, operands) if i == '0')

ans = countdown('5', ['1', '2', '3'])
print(ans)

return value引发StopIteration,调用方不处理它,因此在返回任何内容之前引发异常。

如果return value被这样的yield value; return取代:

1
2
3
4
5
6
7
8
def recursive(value, operands):
    if not operands:
        yield value
        return
    for operator in OPERATORS:
        new_value = str(eval(value + operator + operands[-1]))
        new_operands = operands[:-1]
        yield from recursive(new_value, new_operands)

或者yield value; raise StopIterationyield value; raise StopIteration(value)或循环隐藏在else子句下,然后由调用方按我期望的方式处理异常,函数最终返回'0'。所有这些也都提出了例外:raise StopIteration,包括裸的和有争议的,yield; return value和裸的return

简而言之,当StopIteration被提升而yield从未返回时,调用方中断。

为什么?

PEP380指出,首先处理StopIteration的方式与其他方式不同。PEP47 9表示:

Currently, StopIteration raised accidentally inside a generator function will be interpreted as the end of the iteration by the loop construct driving the generator.

显然,除了第一次。不过,我不清楚底层实现的细节和背后的确切推理。

另外两个问题:

  • 写这段代码的正确方式是什么?回报(收益值)?

  • 这是一个怪癖,一个特写,还是其他什么?

编辑:修复代码错误

edit2:据我所知,第一个代码段中的执行流程如下:

  • countdownrecursive('5', ['1', '2', '3'])创建发电机。
  • 从树下一直到recursive('11', [])都会产生发电机。
  • 此时,StopIteration('11')被提起。
  • 这是最棘手的部分。这里发生了什么?如何处理StopIteration

    第二段:

  • 相同的
  • 相同的
  • '11'向上产生,直至倒计时。
  • if '11' == '0'拒绝
  • 控制流回到yield '11',提升StopIteration
  • 重复,直到'0'
  • 从我现在看到的情况来看,这几乎是人们所期待的行为。StopIteration由它的调用者传入,不向上传播。而调用者则不带参数地引发StopIteration。这就是为什么原始例外中的'11'部分从未达到countdown的原因。第一个片段回溯中的例外是StopIterationcountdown中被recursive('5', ['1', '2', '3']引发。


    当操作数最终用完时,会引发StopIteration。在此之前,您将继续在您的列表中重复出现,评估结果并缩短列表的长度。我认为收益率已经恢复,但它回到了它的调用方,这是以前调用recursive,而不是countdown

    在第二个示例中,您将生成一个值,随后调用recursive将引发StopIteration,因为它会立即返回。

    至于示例return (yield 42),是的,这是一个怪癖。这个问题的海报在制作一个生成器时遇到了障碍,发现他后来认为是错误的代码实际上返回了一些东西。