【问题标题】:Coroutines in Python: Best PracticesPython 中的协程:最佳实践
【发布时间】:2016-03-10 03:18:56
【问题描述】:

我想知道在 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)”语句的问题变得更糟了。

【问题讨论】:

  • 在我看来,您的 both 示例都涉及传入一个函数。此外,您可能想进一步解释为什么要为此使用生成器。您的示例非常基本,以至于它们没有说明使用生成器的任何理由,而不仅仅是让调用者在循环中调用 func 本身。另外,如果第一个示例已经对您有用,您的实际问题是什么?
  • @BrenBarn 我编辑了问题以强调第二种方法不需要传入的函数,这正是我想要的。我的问题是:如何以第二种方法的方式使用生成函数,而不必不断发送“无”对象以推进到正确的产量声明。

标签: python generator coroutine


【解决方案1】:

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

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 表达式时,它首先 em> 产生值 out,然后 yield 表达式的值变为 sent in 之后。

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

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

【讨论】:

    【解决方案2】:

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

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

    此代码使用 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) 
    

    【讨论】:

      猜你喜欢
      • 2014-06-04
      • 1970-01-01
      • 1970-01-01
      • 2014-03-02
      • 1970-01-01
      • 2011-04-25
      • 1970-01-01
      • 1970-01-01
      • 2019-07-12
      相关资源
      最近更新 更多