在Python中从协程中产生一个值,a.k.a。将回调转换为生成器

时间:2015-08-07 04:12:07

标签: python asynchronous functional-programming generator tornado

我是Python和函数式编程的新手。我使用的是2.7.6版本

我使用Tornado框架来发出异步网络请求。根据我对函数式编程的了解,我希望我的数据通过使用生成器来传输我的代码。我使用生成器完成了我需要的大部分工作,并在通过函数调用流转换数据时进行了转换。

在我的流的最后,我想要为某些数据发出REST请求。在我将数据提交给Tornado之前,我有一个for-loop,启动pull,然后发送http请求。 Tornado提供的http对象将回调函数作为一个选项,并且总是返回一个Future - 它实际上是一个Tornado Future对象,而不是官方的Python Future。

我的问题是,由于我现在使用生成器通过我的代码提取数据,我不再想使用回调函数。我的理由是,在从回调中获取数据之后,我的数据现在被推送到我的代码中,我再也无法使用生成器了。

我的目标是创建一个如下所示的界面:

urls = (...generated urls...)
responses = fetch(urls)

响应是完成的网址上的生成器。

我尝试做的事情 - 在许多方面 - 是将回调的结果转换为生成器。我正在考虑这样的事情,尽管我很快就会解决其他问题。但是,我希望我的fetch函数看起来像这样:

def fetch(urls):
    def url_generator():
        while True:
            val = yield
            yield val

    @curry
    def handler(gen, response):
        gen.send(response)

    gen = url_generator()

    for u in urls:
        http.fetch(u, callback=handler(gen))

    return gen

我简化了代码和语法以专注于问题,但我认为这样可以正常工作。我的策略是定义一个协程/生成器,然后我会在收到它们时发送响应。

我最麻烦的是协程/发生器。即使我以上述方式定义一个生成器并执行以下操作,我也会得到一个无限循环 - 这是我的主要问题之一。

def gen():
    while True:
        val = yield
        print 'val', val
        yield val
        print 'after', val
        break

g = gen()
g.send(None)
g.send(10)

for e in g:
    print e

这会在协程中按照预期打印val 10 after 10,但for循环永远不会得到10的值。当中断时,它不会打印任何东西。如果我删除了中断,那么我得到无限循环:

val None
None
after None
None
val None
None
after None
None
...

如果我删除了for循环,那么协程只会在等待第二次产量时打印val 10。我期待这个。但是,使用它并不能产生任何东西。

类似地,如果我删除for循环并用print next(g)替换它,那么我得到一个StopIteration错误,我假设这意味着我在没有更多值的生成器上调用了下一个。

Anywho,当我深入研究Python时,我完全失去了。我认为这是Python中常见的情况,有人知道一个很好的方法。我搜索了将回调转换为生成器'等等,但没有多少运气。

另一方面,我可能会从http请求中获得每个未来,但我没有多少运气和#34;等待"关于未来收益率的完成情况。我阅读了很多关于'来自',但它似乎是Python 3特定的,而Tornado似乎还没有在Python 3上工作。

感谢您查看,感谢您提供的任何帮助。

1 个答案:

答案 0 :(得分:3)

Tornado在Python 3上运行良好。

上面简化代码的问题在于,这不符合您的期望:

val = yield

你希望生成器在那里暂停(阻止你的for循环),直到其他一些函数调用g.send(value),但事实并非如此。相反,代码的行为如下:

val = yield None

因此for循环接收None值的速度与处理它们的速度一样快。收到每个None后,它会隐式调用g.next(),这与g.send(None)相同。所以,你的代码等同于:

def gen():
    while True:
        val = yield None
        print 'val', val
        yield val
        print 'after', val

g = gen()
g.send(None)
g.send(10)

while True:
    try:
        e = g.send(None)
        print e
    except StopIteration:
        break

阅读此版本的代码,其中隐式行为是明确的,我希望很清楚为什么它只是在无限循环中生成None

你需要的是一个函数将一个函数添加到队列头部的方法,而另一个函数阻止等待项目,并在它们准备好时将它们拉出队列的尾部。从Tornado 4.2开始,我们就是这样:

http://www.tornadoweb.org/en/stable/queues.html

网络蜘蛛示例接近您想要做的事情,我相信您可以适应它:

http://www.tornadoweb.org/en/stable/guide/queues.html