如何用Twisted的getPage替代urlopen?

5 投票
2 回答
5666 浏览
提问于 2025-04-15 22:04

我想在一个网页应用中使用Twisted的非阻塞getPage方法,但感觉这个方法用起来比urlopen要复杂很多。

这是我想要实现的一个例子:

def web_request(request):
   response = urllib.urlopen('http://www.example.org')
   return HttpResponse(len(response.read()))

难道要用getPage就这么难吗?

2 个回答

4

我最近在一个类似的问题发表了一个回复,里面提供了使用getPage从网址获取内容所需的最基本代码。为了完整性,这里再贴一下:

from twisted.web.client import getPage
from twisted.internet import reactor

url = 'http://aol.com'

def print_and_stop(output):
    print output
    if reactor.running:
       reactor.stop()

if __name__ == '__main__':
    print 'fetching', url
    d = getPage(url)
    d.addCallback(print_and_stop)
    reactor.run()

请记住,你可能需要更深入地了解Twisted使用的反应器模式,这个模式用来处理事件(在这个例子中,getPage的触发就是一个事件)。

20

关于非阻塞操作(你似乎特别想要的那种),需要明白的是,你不能用它们写出顺序执行的代码。非阻塞的意思是它们不会等待结果,而是启动操作后就把控制权交回给你的函数。所以,getPage 不会像 urllib.urlopen 那样返回一个可以直接读取的文件对象。即使它返回了,你也不能在数据还没准备好之前读取它(那样会造成阻塞)。因此,你不能在它上面调用 len(),因为这需要先读取所有数据(这会阻塞)。

在 Twisted 中处理非阻塞操作的方法是使用 Deferreds,这是一种管理回调的对象。getPage 返回一个 Deferred,这意味着“你稍后会得到这个结果”。在你得到结果之前,你不能对它做任何事情,所以你需要给 Deferred 添加一些 回调,当结果准备好时,Deferred 会调用这些回调。那个回调可以执行你想要的操作:

def web_request(request)
    def callback(data):
        HttpResponse(len(data))
    d = getPage("http://www.example.org")
    d.addCallback(callback)
    return d

你例子中的另一个问题是你的 web_request 函数本身是阻塞的。在你等待 getPage 的结果时,你想做什么?是在 web_request 中做其他事情,还是只是等待?或者你想让 web_request 本身变成非阻塞的?如果是这样,你想怎么产生结果?(在 Twisted 中,明显的选择是返回另一个 Deferred,甚至可以返回和 getPage 返回的同一个。这在你用其他框架写代码时可能不太合适。)

确实有一种方法可以使用 Deferreds 写出顺序执行的代码,虽然这有点限制,调试起来也更困难,而且 Twisted 的核心开发者看到你用它时会感到痛苦:twisted.internet.defer.inlineCallbacks。它利用了 Python 2.5 中的新生成器特性,你可以把数据发送到生成器中,代码看起来大致是这样的:

@defer.inlineCallbacks
def web_request(request)
    data = yield getPage("http://www.example.org")
    HttpResponse(len(data))

就像那个明确返回 dDeferred 的例子一样,这只有在调用者期望 web_request 是非阻塞的情况下才有效——defer.inlineCallbacks 装饰器会把生成器变成一个返回 Deferred 的函数。

撰写回答