【问题标题】:Coroutine vs Continuation vs Generator协程 vs 延续 vs 生成器
【发布时间】:2019-02-12 05:41:31
【问题描述】:

协程与延续和生成器有什么区别?

【问题讨论】:

  • 我想知道协程和延续是否有效等价。我知道可以用延续来建模协程,但是是否可以用协程来建模延续,因为延续严格来说更强大?

标签: python generator coroutine continuations


【解决方案1】:

我将从生成器开始,因为它们是最简单的情况。正如@zvolkov 所提到的,它们是可以重复调用而不返回的函数/对象,但是在调用时将返回(产生)一个值,然后暂停它们的执行。当它们再次被调用时,它们将从上次暂停执行的位置开始并再次执行它们的操作。

生成器本质上是一个缩减(非对称)协程。协程和生成器的区别在于协程可以在初始调用后接受参数,而生成器不能。

想出一个使用协程的简单示例有点困难,但这是我最好的尝试。以这段(编造的)Python代码为例。

def my_coroutine_body(*args):
    while True:
        # Do some funky stuff
        *args = yield value_im_returning
        # Do some more funky stuff

my_coro = make_coroutine(my_coroutine_body)

x = 0
while True:
   # The coroutine does some funky stuff to x, and returns a new value.
   x = my_coro(x)
   print x

使用协程的一个例子是词法分析器和解析器。如果没有语言中的协程或以某种方式模拟,词法分析和解析代码需要混合在一起,即使它们实际上是两个独立的关注点。但是使用协程,您可以将词法分析和解析代码分开。

(我将梳理一下对称协程和非对称协程之间的区别。只要说它们是等价的就足够了,你可以从一个转换到另一个,以及非对称协程——它们最像生成器—— - 更容易理解。我正在概述如何在 Python 中实现非对称协程。)

Continuations 实际上是非常简单的野兽。它们都是代表程序中另一个点的函数,如果你调用它,将导致执行自动切换到函数代表的点。您每天都在使用非常受限的版本,甚至没有意识到这一点。例如,异常可以被认为是一种由内而外的延续。我会给你一个基于 Python 的延续伪代码示例。

假设 Python 有一个名为 callcc() 的函数,该函数有两个参数,第一个是函数,第二个是调用它的参数列表。该函数的唯一限制是它接受的最后一个参数将是一个函数(这将是我们当前的延续)。

def foo(x, y, cc):
   cc(max(x, y))

biggest = callcc(foo, [23, 42])
print biggest

会发生的情况是callcc() 会依次调用foo() 并使用当前的延续(cc),即对程序中调用callcc() 的点的引用。当foo() 调用当前延续时,它本质上与告诉callcc() 返回您调用当前延续所用的值相同,当它这样做时,它将堆栈回滚到创建当前延续的位置,即当你打电话给callcc()时。

所有这些的结果将是我们假设的 Python 变体将打印 '42'

我希望这会有所帮助,我相信我的解释可以改进很多!

【讨论】:

  • 一点点:delimited 延续是函数,但 undelimited 延续不是:okmij.org/ftp/continuations/undelimited.html#delim-vs-undelim
  • 这很好。也就是说,在大多数实际应用中,当人们说“延续”时,他们指的是部分/定界延续。引入各种其他类型的延续会使解释有些混乱。
  • 延续不是函数,尽管它们可以被具体化为函数。 “也就是说,在大多数实际应用中,当人们说‘延续’时,他们指的是部分/定界的延续。”您会指出“延续”一词的这种用法吗?我从来没有遇到过这样的用法。您还通过使用 call/cc 给出了一个不定界延续的示例。分隔延续的运算符通常是“reset”和“shift”(它们可能有其他名称)。
  • 让我们从我写这篇文章已经五年开始。你参加聚会有点晚了。其次,我知道未定界的延续不是函数,但是你试着解释它们是如何工作的,而不是这样引用它们,同时保持语言简单明了。从普通程序员的角度来看,未定界的延续不返回这一事实只会使其成为一次性函数,根据函数的定义,这不是正确 ,但至少是可以理解的
  • 我没有迟到,因为这是我在谷歌搜索“协程与生成器”时得到的第一个结果。我希望能找到一些关于他们差异的好信息。反正我是在别处找到的。而且我不是第一个指出您对延续的解释是错误的。问题是有人会弄错,以后当她或他遇到用于不同事物的同一个词时可能会感到困惑。
【解决方案2】:

协程是轮流执行其工作然后暂停以将控制权交给组中的其他协程的几个程序之一。

Continuation 是传递给某个过程的“指向函数的指针”,当该过程完成时将被执行(“继续”)。

生成器(在 .NET 中)是一种语言结构,可以吐出一个值,“暂停”方法的执行,然后在被要求输入下一个值时从同一点继续。

【讨论】:

  • 我意识到答案可能不准确,但在这个级别的问题中,我尽量保持简单。此外,我自己也不是很了解这一切:)
  • python 中的生成器类似于 C# 版本,但实现为创建迭代器对象实例的特殊语法,它返回由您提供的“函数”定义返回的值。
  • 一个小的更正:“...包括调用堆栈和所有变量,但不包括它们的值”(或者只是删除“所有变量”)。延续不保留值,它们只包含调用堆栈。
  • 不,延续不是“指向函数的指针”。在最简单的实现中,它包含一个指向函数的指针和一个保存局部变量的环境。而且它永远不会返回,除非你使用 call/cc 之类的东西来捕获它并返回值。
【解决方案3】:

在较新版本的 Python 中,您可以使用 generator.send() 向生成器发送值,这使得 python 生成器有效地协程。

python 生成器和其他生成器(比如 greenlet)的主要区别在于,在 python 中,您的 yield value 只能返回给调用者。在 greenlet 中,target.switch(value) 可以将您带到特定的目标协程并产生一个值,target 将继续运行。

【讨论】:

  • 但在 Python 中,所有的yield 调用必须在同一个函数中,该函数称为“生成器”。你不能在子函数中yield,这就是为什么 Python 被称为 semi-coroutines,而 Lua 有 asymmetric coroutines。 (有提高产量的建议,但我认为那些只会使水变得浑浊。)
  • @cdunn2001:(Winston 评论)Python3.3 引入了“yield from”表达式,让您可以从子生成器中产生。
猜你喜欢
  • 2012-02-19
  • 2016-07-20
  • 1970-01-01
  • 2015-04-20
  • 2020-02-17
  • 2012-04-23
  • 2010-10-06
相关资源
最近更新 更多