Python生成器和协同程序

Giu*_*ore 9 python generator coroutine

我正在研究各种编程语言的协同程序和生成器.

我想知道是否有一种更简洁的方法将通过生成器实现的两个协同程序组合在一起,而不是在被调用者产生的情况下向调用者返回?

假设我们使用以下约定:除最后一个之外的所有产量都返回null,而最后一个返回coroutine的结果.因此,例如,我们可以使用一个调用另一个协程的协程:

def A():
  # yield until a certain condition is met
  yield result

def B():
  # do something that may or may not yield
  x = bind(A())
  # ...
  return result
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我希望通过绑定(可能或可能不可实现,这是问题),协程B在A收益直到A返回其最终结果时产生,然后将其分配给x以允许B继续.

我怀疑实际的代码应该显式迭代A所以:

def B():
  # do something that may or may not yield
  for x in A(): ()
  # ...
  return result
Run Code Online (Sandbox Code Playgroud)

这有点丑陋且容易出错......

PS:这是一种游戏,其中语言的用户将是编写脚本的设计者(script = coroutine).每个字符都有一个关联的脚本,并且有许多子脚本由主脚本调用; 例如,考虑到run_ship多次调用reach_closest_enemy,fight_with_closest_enemy,flee_to_allies等等.所有这些子脚本都需要按照上面描述的方式调用; 对于开发人员来说,这不是问题,但对于设计人员而言,他们编写的代码越少越好!

Sim*_*ord 17

编辑:我建议使用Greenlet.但如果您对纯Python方法感兴趣,请继续阅读.

这在PEP 342中得到了解决,但起初有点难以理解.我将尝试简单解释它是如何工作的.

首先,让我总结一下我认为你真正试图解决的问题.

问题

你有一个生成器函数的callstack调用其他生成器函数.你真正想要的是能够从顶部的发电机中产生,并使产量一直沿着堆栈传播.

问题是Python(在语言级别)不支持真正的协程,只支持生成器.(但是,它们可以实现.)真正的协同程序允许您暂停整个函数调用堆栈并切换到不同的堆栈.生成器仅允许您暂停单个功能.如果生成器f()想要产生,则yield语句必须在f()中,而不是在f()调用的另一个函数中.

我认为你现在使用的解决方案是做类似于Simon Stelling的回答(即通过产生g()的所有结果来调用g().这是非常冗长和丑陋的,你正在寻找语法糖来包装这种模式.请注意,这实际上是在每次屈服时展开堆栈,然后再将其重新卷起.

有一种更好的方法可以解决这个问题.您基本上通过在"trampoline"系统上运行您的生成器来实现协同程序.

要使这项工作,您需要遵循几种模式:1.当您想要调用另一个协同程序时,请将其生成.而不是返回一个值,而是产生它.

所以

def f():
    result = g()
    # …
    return return_value
Run Code Online (Sandbox Code Playgroud)

def f():
    result = yield g()
    # …
    yield return_value
Run Code Online (Sandbox Code Playgroud)

说你在f().蹦床系统叫做f().当你产生一个发电机(比如g())时,蹦床系统会代表你调用g().然后当g()完成产生值时,trampoline系统重新启动f().这意味着你实际上并没有使用Python堆栈; 蹦床系统管理一个callstack.

当您生成除生成器之外的其他内容时,trampoline系统会将其视为返回值.它通过yield语句(使用生成器的.send()方法)将该值传递回调用者生成器.

评论

这种系统在异步应用程序中非常重要和有用,例如那些使用Tornado或Twisted的系统.您可以在阻塞时停止整个callstack,再执行其他操作,然后返回并继续执行它停止的第一个callstack.

上述解决方案的缺点是它要求您基本上将所有函数编写为生成器.为Python使用真正的协同程序可能更好 - 见下文.

备择方案

Python有多种协程实现,请参阅:http://en.wikipedia.org/wiki/Coroutine#Implementations_for_Python

Greenlet是一个很好的选择.它是一个Python模块,通过交换callstack来修改CPython解释器以允许真正的协同程序.

Python 3.3应提供委托给子生成器的语法,请参阅PEP 380.

  • 对于在2015年或之后阅读此内容的任何人来说,新语法是'yield from'([PEP 380](https://www.python.org/dev/peps/pep-0380/))并且它允许Python中的真正协同程序> 3.3. (4认同)

blu*_*ubb 2

您在寻找这样的东西吗?

def B():
   for x in A():
     if x is None:
       yield
     else:
       break

   # continue, x contains value A yielded
Run Code Online (Sandbox Code Playgroud)