Python生成器在单独的函数中的'yield'

时间:2011-09-01 11:24:26

标签: python google-app-engine generator

我正在实现一个实用程序库,它是一种旨在在Google App Engine云计算服务的分布式环境中运行的任务管理器。 (它使用任务队列和内存缓存的组合来执行后台处理)。我计划使用生成器来控制任务的执行,实际上是通过在用户代码中使用yield强制执行非抢占式“并发”。

简单的例子 - 处理一堆数据库实体 - 可能类似于以下内容:

class EntityWorker(Worker):
    def setup():
        self.entity_query = Entity.all()
    def run():
        for e in self.entity_query:
            do_something_with(e)
            yield

如我们所知,yield是双向通信通道,允许将值传递给使用生成器的代码。这允许模拟“抢占式API”,例如下面的SLEEP调用:

def run():
    for e in self.entity_query:
        do_something_with(e)
        yield Worker.SLEEP, timedelta(seconds=1)

但这很难看。将yield隐藏在单独的函数中会很好,这可以用简单的方式调用:

self.sleep(timedelta(seconds=1))

问题是将yield置于函数sleep会将转换为生成器函数。因此上面的调用只会返回另一个生成器。只有在再次添加.next()yield之后,我们才会获得以前的结果:

yield self.sleep(timedelta(seconds=1)).next()

当然,这在以前更加难看,而且不必要地冗长。

因此我的问题是:有没有办法将yield置于函数中而不将其转换为生成函数,但是让其他生成器可以使用它来生成由它计算的值?

3 个答案:

答案 0 :(得分:3)

你似乎错过了显而易见的事情:

class EntityWorker(Worker):
    def setup(self):
        self.entity_query = Entity.all()

    def run(self):
        for e in self.entity_query:
            do_something_with(e)
            yield self.sleep(timedelta(seconds=1))

    def sleep(self, wait):
        return Worker.SLEEP, wait

yield将函数转换为生成器,不可能将其遗漏。

要隐藏收益,您需要更高阶函数,在您的示例中为map

from itertools import imap

def slowmap(f, sleep, *iters):
    for row in imap(f, self.entity_query):
        yield Worker.SLEEP, wait

def run():
    return slowmap(do_something_with, 
                   (Worker.SLEEP, timedelta(seconds=1)),
                   self.entity_query)

答案 1 :(得分:2)

唉,这不行。但是“中间路线”可能会很好:

def sleepjob(*a, **k):
    if a:
        return Worker.SLEEP, a[0]
    else:
        return Worker.SLEEP, timedelta(**k)

所以

yield self.sleepjob(timedelta(seconds=1))
yield self.sleepjob(seconds=1)

对我来说没问题。

答案 2 :(得分:1)

我建议您查看ndb。它使用生成器作为协同例程(正如您在此处提出的那样),允许您编写异步处理rpcs的程序。

api通过使用另一个“填充”生成器的函数包装生成器来实现这一点(它立即调用.next()以便代码开始执行)。这些tasklet还可以与App Engine的rpc基础架构配合使用,从而可以使用任何现有的异步api调用。

使用ndb中使用的concurreny模型,您yield可以是未来对象(类似于pep-3148中描述的对象)或App Engine rpc对象。当该rpc完成时,允许继续产生该对象的函数中的执行。

如果您使用的是从ndb.model.Model派生的模型,那么以下内容将允许您异步迭代查询:

from ndb import tasklets

@tasklets.tasklet
def run():
it = iter(Entity.query())
# Other tasklets will be allowed to run if the next call has to wait for an rpc.
while (yield it.has_next_async()):
  entity = it.next()
  do_something_with(entity)

虽然ndb仍然被认为是实验性的(它的一些错误处理代码仍然需要一些工作),我建议你看看它。我在最近的两个项目中使用它,发现它是一个很棒的库。

请务必仔细阅读主页中链接的文档以及tasklet stuff的配套文档。