繁体   English   中英

Python 中的协程:最佳实践

[英]Coroutines in Python: Best Practices

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

我发现的第一种方法基本上是执行以下操作:

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(None)”之后,以将生成器推进到下一个产量。

在我看来,这里的问题源于用于两个不同事物的“yield”关键字:返回语句和输入语句。

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


请注意:实际上,我将发送多个值。 因此,具有无关的“g.send(None)”语句的问题变得更糟。

您可以像在第一个示例中那样进行操作。 您只需要在循环内“处理数据”即可。 这是一个例子:

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表达式时,它首先产生值,然后产生yield表达式成为之后sent的任何内容。

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

正如我在评论中提到的,从您的示例中不清楚您为什么要使用生成器。 在许多情况下,您可以通过编写一个类来实现类似的效果,该类具有自己的传入和传出方法,如果您编写类,您可以随心所欲地制作 API。 如果您选择使用生成器,则必须接受sendyield的双重输入/输出角色。 如果您不喜欢这样,请不要使用生成器(或者,如果您需要它们提供的挂起功能状态,则可以使用它们,但用一个将发送与产生分开的类包装它们)。

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

当您使用 send() 方法时,它将在执行暂停的 yield 处进行分配(但不从该 yield 返回结果),然后继续到下一个 yield ,此时将返回一个值并执行暂停。 这在以下图形和示例代码中进行了演示。 下面是用于同步硬件系统的建模和验证的设计模式,创建的设计组件可以占用 M 个输入并在每次迭代时提供 N 个输出,并演示了我描述的操作:

消费者生产者步骤 1

消费者生产者步骤 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

这是一个额外的示例,显示了循环中单个 yield 的相同行为:

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) 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM