Python生成器和协同程序

时间:2011-05-10 10:25:36

标签: 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

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

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

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

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

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

2 个答案:

答案 0 :(得分:16)

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

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

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

问题

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

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

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

解决方案

有一种更好的方法可以解决这个问题。你基本上通过在“蹦床”系统上运行你的发电机来实现协同程序。

要完成这项工作,您需要遵循以下几种模式: 当你想打电话给另一个协程时,就把它收起来。 2.不要返回一个值,而是产生它。

所以

def f():
    result = g()
    # …
    return return_value

变为

def f():
    result = yield g()
    # …
    yield return_value

说你在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

答案 1 :(得分:2)

你在找这样的东西吗?

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

   # continue, x contains value A yielded