在Python中从协程中产生一个值,也就是将回调转换为生成器

Joe*_*Joe 9 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)
Run Code Online (Sandbox Code Playgroud)

响应是完成的URL上的生成器.

我尝试做的事情 - 在许多方面 - 是将回调的结果转换为生成器.我正在考虑这样的事情,尽管我很快就会解决其他问题,我很快就会解释.但是,我希望我的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
Run Code Online (Sandbox Code Playgroud)

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

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

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
Run Code Online (Sandbox Code Playgroud)

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

val None
None
after None
None
val None
None
after None
None
...
Run Code Online (Sandbox Code Playgroud)

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

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

无论如何,当我深入研究Python时,我完全失去了.我认为这是Python中常见的情况,有人知道一个很好的方法.我搜索'转换回发电机'等等,但没有太多运气.

另一方面,我可能会从http请求中获得每个未来,但我没有太多运气"等待"未来完成的收益率.我读了很多关于'yield from'的内容,但它似乎是Python 3特有的,而Tornado似乎还没有在Python 3上运行.

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

A. *_*vis 4

Tornado 在 Python 3 上运行得很好。

上面简化代码的问题是这没有达到您的预期:

val = yield
Run Code Online (Sandbox Code Playgroud)

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

val = yield None
Run Code Online (Sandbox Code Playgroud)

因此,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
Run Code Online (Sandbox Code Playgroud)

阅读这个版本的代码,其中隐式行为变得显式,我希望清楚为什么它只是None在无限循环中生成。

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

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

网络蜘蛛示例与您想要做的很接近,我相信您可以调整它:

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