【问题标题】:How does Python work with multiple "send" calls to a generator?Python 如何处理对生成器的多个“发送”调用?
【发布时间】:2018-05-30 18:27:19
【问题描述】:

有很多关于类似问题的好问题,例如

python generator "send" function purpose?

What does the "yield" keyword do?

让我们回到“发送”的定义:

恢复执行并将一个值“发送”到生成器函数中。 value 参数成为当前 yield 表达式的结果。 send() 方法返回生成器产生的下一个值,或者 如果生成器退出而没有产生另一个,则引发 StopIteration 价值。当调用 send() 启动生成器时,必须调用 以 None 作为参数,因为没有 yield 表达式 可以收到价值

但我觉得我错过了一些重要的事情。这是我的示例,其中包含 3 个 send 调用,包括带有 None 值的初始调用,用于初始化生成器:

def multiplier():
    while True:
        m = yield               # Line #3
        print('m = ' + str(m))  # Line #4
        yield str(m * 2)        # Line #5
        yield str(m * 3)        # Line #6

#------------------------

it = multiplier()

print('it.send(None): ')
print(str(it.send(None)))
print('--------------')

print('it.send(10): ')
print(it.send(10))
print('--------------')

print('it.send(100): ')
print(it.send(100))
print('--------------')

这是一个输出:

it.send(None): 
None
--------------
it.send(10): 
m = 10
20
--------------
it.send(100): 
30
--------------

问题:

  • 当我在第 5 行中使用 it.send(10) 时会发生什么。要是我们 按照定义,生成器继续执行。发电机 接受10 作为输入值并在当前yield expression 中使用它。在我的例子中是yield str(m * 2),但是m 设置为10。什么时候发生的。是不是因为 第 3 行中 myield 之间的引用?

  • 第 6 行 it.send(10) 发生了什么,为什么输出仍然是 30? 这是否意味着我上一个问题中的参考仅有效 一次?

注意: 如果我更改了示例并在第 5 行和第 6 行之间添加了一行 m = yield,然后在 print(it.send(10)) 之后使用 next(it) - 在这种情况下,输出开始有意义:20 和 300

【问题讨论】:

  • 您的代码中只有一个yield 表达式:m = yield。即使您发送了一个值,m 也不会被重新分配,直到 while 循环的下一次迭代。
  • @vaultah 什么时候设置?在第一次或第二次send 通话之后?第一个send 只是yield-s 和第二个更改/设置yield 值并将其分配给m,然后在第二个“yield”调用后停止?
  • @Axalix 在第一个 send 之前,生成器中没有执行任何操作。第一次调用send 时,生成器会一直执行,直到输入m = yield 中的yield。在第二个sendyield 返回参数send 并继续执行直到输入yield str(m * 2)
  • @MichaelButscher 这对我来说很有意义,但是 Python 在什么时候为 m 赋值?第二个send 电话会发生这种情况吗?
  • @Axalix 是的。第二个sendyield 表达式返回参数值,然后将值分配给m

标签: python python-3.x


【解决方案1】:

您的生成器函数具有三个yield 表达式,但是您丢弃了其中两个(第5 行和第6 行)的值。如果你对那里的值做了什么,你会看到函数中使用了100。如果您继续运行您的示例,您第五次调用send 将导致生成器将m 更新为新值。

让我们看一下在您的示例中执行 send 调用的代码,看看生成器同时在做什么:

it = multiplier()

此时生成器对象已创建并保存到it。生成器代码还没有开始运行,它在函数代码的开始处暂停。

print(str(it.send(None)))

这将开始运行生成器函数的代码。发送的值必须是 None 否则你会得到一个错误。该函数永远不会看到该值。使用next 来启动生成器更为常见,因为next(it) 等同于it.send(None)

生成器函数一直运行到第 3 行,第一个 yield 出现在该处。由于您没有产生任何特定值,因此来自send 的返回值是None(被打印出来)。

print(it.send(10))

这个值被发送到生成器并成为第 3 行的yield 表达式的值。所以10 被存储为m,代码在第 4 行打印出来。生成器函数继续运行到第 5 行,在那里它到达下一个 yield 表达式。因为它产生了str(m * 2),所以调用代码得到"20"并打印出来。

print(it.send(100))

100 值作为第 4 行上的 yield 的值发送到生成器。该值被忽略,因为您没有将 yield 用作表达式,而是用作语句。就像将100 单独放在一条线上一样,这是完全合法的,但可能不是很有用。代码继续到第 5 行,生成str(m * 3)"30",由调用代码打印。

这就是您的驱动代码停止的地方,但生成器仍然存在,您可以向它发送更多值(并获取更多值)。 send 生成器的下一个值也将被忽略,就像 100 一样,但是当生成器中的 while 循环返回到顶部和第 3 行 yield 已到达。

我怀疑您在这段代码中对send 的一些混淆与您将yield 用作表达式和语句的事实有关。可能你不想两者都做。通常,您要么关心发送到生成器的所有值,要么不关心它们中的任何一个。如果您想同时产生多个值(如 n*2n*3),您可以产生一个元组而不是单个项目。

这是您的代码的修改版本,我认为您可能更容易使用和理解:

def multiplier():
    print("top of generator")
    m = yield # nothing to yield the first time, just a value we get
    print("before loop, m =", m)
    while True:
        print("top of loop, m =", m)
        m = yield m * 2, m * 3         # we always care about the value we're sent
        print("bottom of loop, m =", m)

print("calling generator")
it = multiplier()

print("calling next")
next(it)   # this is equivalent to it.send(None)

print("sending 10")
print(it.send(10))

print("sending 20")
print(it.send(20))

print("sending 100")
print(it.send(100))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-03
    • 1970-01-01
    • 2019-11-07
    • 2015-03-16
    • 2021-10-22
    • 2020-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多