Python中的协程:最佳实践

时间:2015-12-05 03:57:23

标签: python generator coroutine

我想知道在Python 3中编写协同程序的最佳实践是什么。我正在开发基本方法,它应该接受一些输入(使用.send()方法),对此输入执行计算,然后产生输出。 / p>

我发现的第一种方法基本上是做以下事情:

def coroutine(func):
  data = yield
  while 1:
    data = yield func(data)

这似乎有效,但循环中的界限让我大吃一惊。它似乎首先产生一个函数,然后接受输入并在恢复后执行赋值。这对我来说完全不直观。

我正在研究的另一种方法是:

def coroutine():
  while 1:
    data = yield
    [ do stuff with data here ... ]
    yield result

这个代码对我来说更容易理解,它还允许我将代码放入生成器而不是传递函数。但使用起来很烦人。每次对生成器的实际调用(如" gen.send(2)")必须后跟一个" gen.send(无)"将发电机推进到下一个产量。

在我看来,这里的问题源于"产量"关键字用于两个不同的东西:return语句和输入语句。

如果可能的话,我想要一种方法,让我接受输入,对输入进行计算,然后产生输出,而不必像第一种方法那样传递函数和使用单行,或者必须发送无关值在第二种方法。我怎么能这样做?

请注意:实际上,我将发送多个值。所以存在无关的问题" g.send(无)"陈述变得更糟。

2 个答案:

答案 0 :(得分:7)

你可以像在第一个例子中那样做。你只需要在循环中“对数据进行处理”。这是一个例子:

def coroutine():
  data = yield
  while True:
    print("I am doing stuff with data now")
    data = data * 2
    data = yield data

你可以像这样使用它:

>>> co = coroutine()
>>> next(co)
>>> co.send(1)
I am doing stuff with data now
2
>>> co.send(88)
I am doing stuff with data now
176

你是正确的yield扮演双重角色,既产生结果又接受随后通过send传递的值。 (同样地,send扮演双重和互补角色,因为每个send调用都会返回生成器产生的值。)请注意那里的顺序:当你有一个yield表达式时,它 first 会产生值,然后yield表达式的值变为之后的sent

这似乎是“倒退”,但你可以通过循环来实现它“前进”,就像你基本上已经做到的那样。这个想法是你首先产生一些初始值(也许是无意义的)。这是必要的,因为在产生值之前不能使用send(因为没有yield表达式来评估发送的值)。然后,每次使用yield时,您都会给出“当前”值,同时接受用于计算“下一个”值的输入。

正如我在评论中提到的那样,从你的例子中不清楚你为什么要使用发电机。在许多情况下,只需编写一个具有自己的方法来传递和解决问题的类就可以实现类似的效果,如果你编写了类,你可以随意创建API。如果选择使用生成器,则必须接受sendyield的双输入/输出角色。如果你不喜欢它,不要使用生成器(或者,如果你需要它们提供的挂起函数状态,你可以使用它们,但是用一个将发送和收益分开的类来包装它们。)

答案 1 :(得分:0)

为 BrenBarn 的回答添加一个重要的澄清:这句话“当你有一个 yield 表达式时,它首先将值输出,然后 yield 表达式的值成为之后发送的任何值。”不完全准确,只发生在他给出的例子中,因为在循环中使用了相同的产量。实际发生的是首先进行 yield 分配(在程序暂停的 yield 处),然后继续执行下一个 yield 并返回其结果。

当您使用 send() 方法时,它将在执行暂停的 yield 处进行赋值(但不会从 THAT yield 返回结果),然后继续到下一个 yield,此时将返回一个值并且执行将暂停。下面的图形和示例代码演示了这一点:

consumer producer step 1

consumer producer step 2

此代码使用 Python 3.8 演示/确认上述操作:

def GenFunc():
    x = 'a'
    in1 = yield x
    y = 'b'
    print(f"After first yield: {in1=}, {y=}")
    in2 = yield y
    z = 'c'
    print(f"After second yield: {in1=}, {in2=}")
    in3 = yield z
    print(f"After third yield: {in1=}, {in2=}, {in3=}")

执行如下:

>>> mygen = GenFunc()
>>> next(mygen)
Out: 'a'
>>> mygen.send(25)
After first yield: in1=25, y='b'
Out: 'b'
>>> mygen.send(15)
After second yield: in1=25, in2=15
Out: 'c'
>>> mygen.send(45)
After third yield: in1=25, in2=15, in3=45
-----------------------------
StopInteration Error

这里是一个附加示例,显示了在循环中使用单个产量的相同行为:

def GenFunc(n):
    x = 0
    while True:
    n += 1
    x = yield n,x 
    x += 1
    print(n,x)
    x += 1

执行如下:

>>> mygen = GenFunc(10)
>>> next(mygen)
Out: (11, 0)
>>> mygen.send(5)
11 6
Out: (12, 7)