【问题标题】:Python : Behaviour of send() in generatorsPython:生成器中 send() 的行为
【发布时间】:2016-08-28 01:58:26
【问题描述】:

我在 python 3 中尝试使用生成器并编写了这个相当做作的生成器:

def send_gen():
    print("    send_gen(): will yield 1")
    x = yield 1
    print("    send_gen(): sent in '{}'".format(x))
    # yield  # causes StopIteration when left out


gen = send_gen()
print("yielded {}".format(gen.__next__()))

print("running gen.send()")
gen.send("a string")

输出:

    send_gen(): will yield 1
yielded 1
running gen.send()
    send_gen(): sent in 'a string'
Traceback (most recent call last):
  File "gen_test.py", line 12, in <module>
    gen.send("a string")
StopIteration

所以gen.__next__() 到达x = yield 1 行并产生1。我认为x 将分配给None,然后gen.send() 将寻找下一个 yield 语句因为x = yield 1 被“使用”了,然后得到一个StopIteration

相反,似乎发生的事情是x被发送“一个字符串”,它被打印出来,然后然后python尝试寻找下一个@ 987654332@ 并获得StopIteration

所以我试试这个:

def send_gen():
    x = yield 1
    print("    send_gen(): sent in '{}'".format(x))


gen = send_gen()
print("yielded : {}".format(gen.send(None)))

输出:

yielded : 1

但现在没有错误。在将x 分配给None 之后,send() 似乎没有尝试查找 next yield 语句。

为什么行为略有不同?这与我如何启动生成器有关吗?

【问题讨论】:

    标签: python python-3.x generator yield coroutine


    【解决方案1】:

    行为是不同的;在第二个设置中,您从未超越生成器中的第一个 yield 表达式。请注意,StopIteration 不是错误;这是正常行为,每当生成器结束时都会触发预期的信号。在您的第二个示例中,您只是从未到达生成器的末尾。

    每当生成器到达yield 表达式时,执行就会暂停就在那儿,表达式无法在生成器内产生任何东西,直到它被恢复。 gen.__next__()gen.send() 都将从该点恢复执行,yield 表达式将生成由 gen.send()None 传入的值。如果有帮助,您可以将gen.__next__() 视为gen.send(None)。这里要意识到的一件事是gen.send()yield 返回发送的值首先,然后然后生成器继续到下一个yield

    因此,鉴于您的第一个示例生成器,会发生这种情况:

    1. gen = send_gen() 创建生成器对象。代码在函数的最顶部暂停,没有执行任何操作。

    2. 您可以拨打gen.__next__()gen.send(None);生成器开始执行,直到第一个 yield 表达式:

      print("    send_gen(): will yield 1")
      yield 1
      

      现在执行暂停gen.__next__()gen.send(None) 调用现在返回 1,这是由 yield 1 产生的值。因为生成器现在暂停,x = ... 分配还不能发生!只有在生成器再次恢复时才会发生这种情况。

    3. 您在第一个示例中调用 gen.send("a string"),不要在第二个示例中调用 any。所以对于第一个例子,现在恢复生成器函数:

      x = <return value of the yield expression>  # 'a string' in this case
      print("    send_gen(): sent in '{}'".format(x))
      

      现在函数结束,所以StopIteration 被提升了。

    因为您在第二个示例中从未恢复过生成器,所以没有到达生成器的末尾,也没有引发 StopIteration 异常。

    请注意,由于生成器从函数的顶部开始,因此此时没有 yield 表达式来返回您使用 gen.send() 发送的任何内容,因此第一个 gen.send() 值必须始终为 None 或引发异常。最好使用显式的gen.__next__()(或者,更确切地说是next(gen) 函数调用)来“启动”生成器,这样它就会在第一个yield 表达式处暂停。

    【讨论】:

    • 啊,我明白了。 x = ... 的解释帮助很大。谢谢!
    • 执行是在 yield 分配之前还是之后暂停?我认为在x = yield 1 中,yield 1 和 assignment(=) 是两个独立的步骤。如果我错了,请纠正我。
    • @nn0p 它们是两个独立的步骤。 yield 1 暂停,直到生成器恢复,此时表达式结果被分配给 x
    【解决方案2】:

    这里的关键区别在于您在第一个示例中点击了生成器两次,但您在第二个示例中只点击了生成器一次

    当你定义一个协程,即你打算向其中发送参数的生成器时,你必须通过前进到第一个yield语句来预先“准备”它。只有这样,您才能发送值。在第一个示例中,您已通过在尝试 send 之前调用 gen.__next__() 显式完成此操作。

    在第二个示例中,您还通过执行gen.send(None) 来启动它(请注意,发送None 实际上相当于调用gen.__next__()next(gen))。但是你没有尝试第二次发送值,所以在那种情况下没有StopIteration。生成器只是在 yield 语句处暂停,等待你再次点击它,这也是你之后还没有看到打印的原因。

    还有一点需要注意,如果您在第二个示例中发送了除None 以外的任何内容,则会出现错误:

    TypeError: can't send non-None value to a just-started generator
    

    这就是我所说的“启动”协程。

    【讨论】:

      【解决方案3】:

      这在技术上是一个协同程序,而不是一个生成器:

      1. 当我们调用 send_gen() 时,会创建协程对象
      2. 当调用 gen.___next____() [我们应该使用 next(gen)] 时,将调用生成器函数,该函数返回 1 并阻塞
      3. 当 gen.send("a string") 被调用时,协程被唤醒并处理输入(这里打印“a string”)
      4. 然后协程退出

      【讨论】:

        猜你喜欢
        • 2018-01-19
        • 1970-01-01
        • 2019-01-09
        • 2012-05-30
        • 2021-09-19
        • 1970-01-01
        • 2020-11-22
        • 2014-10-21
        相关资源
        最近更新 更多