[英]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。 如果您选择使用生成器,则必须接受send
和yield
的双重输入/输出角色。 如果您不喜欢这样,请不要使用生成器(或者,如果您需要它们提供的挂起功能状态,则可以使用它们,但用一个将发送与产生分开的类包装它们)。
为 BrenBarn 的回答添加一个重要的说明:句子“当你有一个 yield 表达式时,它首先产生值,然后 yield 表达式的值变成之后发送的任何值。” 并不完全准确,仅在他给出的示例中发生,因为循环中使用了相同的产量。 实际发生的是先进行 yield 分配(在程序暂停的 yield 处),然后继续执行下一个 yield 并返回其结果。
当您使用 send() 方法时,它将在执行暂停的 yield 处进行分配(但不从该 yield 返回结果),然后继续到下一个 yield ,此时将返回一个值并执行暂停。 这在以下图形和示例代码中进行了演示。 下面是用于同步硬件系统的建模和验证的设计模式,创建的设计组件可以占用 M 个输入并在每次迭代时提供 N 个输出,并演示了我描述的操作:
此代码使用 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.