一般来说,如果您尝试以“阻塞”方式使用 Twisted,您会遇到很多困难,因为这既不是它的预期使用方式,也不是大多数人们使用它。
顺其自然要容易得多,在这种情况下,这意味着拥抱回调。您的问题的回调式解决方案如下所示:
import re
from twisted.internet import reactor, protocol
from twisted.words.protocols import irc
find_command = re.compile(r'google ([a-z]+)').findall
class Googler(irc.IRCClient):
def privmsg(self, user, channel, message):
for text in find_command(message):
self.say(channel, "http://google.com/search?q=%s" % (text,))
def connect():
cc = protocol.ClientCreator(reactor, Googler)
return cc.connectTCP(host, port)
def run(proto):
proto.join(channel)
def main():
d = connect()
d.addCallback(run)
reactor.run()
这不是绝对必需的(但我强烈建议您考虑尝试一下)。一种选择是inlineCallbacks:
import re
from twisted.internet import reactor, protocol, defer
from twisted.words.protocols import irc
find_command = re.compile(r'google ([a-z]+)').findall
class Googler(irc.IRCClient):
def privmsg(self, user, channel, message):
for text in find_command(message):
self.say(channel, "http://google.com/search?q=%s" % (text,))
@defer.inlineCallbacks
def run():
cc = protocol.ClientCreator(reactor, Googler)
proto = yield cc.connectTCP(host, port)
proto.join(channel)
def main():
run()
reactor.run()
不再注意addCallbacks。在修饰的生成器函数中,它已被 yield 取代。如果您有一个具有不同 API 的 Googler 版本,这可能会更接近您的要求(上面的那个应该与来自 Twisted 的 IRCClient 一起使用,因为它是编写的 - 尽管我没有测试它)。 Googler.join 完全有可能返回某种 Channel 对象,并且 Channel 对象可以像这样迭代:
@defer.inlineCallbacks
def run():
cc = protocol.ClientCreator(reactor, Googler)
proto = yield cc.connectTCP(host, port)
channel = proto.join(channel)
for msg in channel:
msg = yield msg
for text in find_command(msg):
channel.say("http://google.com/search?q=%s" % (text,))
只需在现有 API 之上实现此 API。当然,yield 表达式仍然存在,我不知道这会让你感到多么不安。 ;)
可以进一步远离回调,并使异步操作所需的上下文切换完全不可见。这很糟糕,因为同样的原因,你家外面的人行道上到处都是看不见的捕熊器也会很糟糕。但是,这是可能的。使用 corotwine 之类的东西,它本身基于 CPython 的第三方协程库,您可以让 Channel 的实现自己进行上下文切换,而不需要调用应用程序代码来做。结果可能类似于:
from corotwine import protocol
def run():
proto = Googler()
transport = protocol.gConnectTCP(host, port)
proto.makeConnection(transport)
channel = proto.join(channel)
for msg in channel:
for text in find_command(msg):
channel.say("http://google.com/search?q=%s" % (text,))
Channel 的实现可能类似于:
from corotwine import defer
class Channel(object):
def __init__(self, ircClient, name):
self.ircClient = ircClient
self.name = name
def __iter__(self):
while True:
d = self.ircClient.getNextMessage(self.name)
message = defer.blockOn(d)
yield message
这又依赖于新的Googler 方法getNextMessage,它是基于现有IRCClient 回调的简单功能添加:
from twisted.internet import defer
class Googler(irc.IRCClient):
def connectionMade(self):
irc.IRCClient.connectionMade(self)
self._nextMessages = {}
def getNextMessage(self, channel):
if channel not in self._nextMessages:
self._nextMessages[channel] = defer.DeferredQueue()
return self._nextMessages[channel].get()
def privmsg(self, user, channel, message):
if channel not in self._nextMessages:
self._nextMessages[channel] = defer.DeferredQueue()
self._nextMessages[channel].put(message)
要运行它,你需要为run 函数创建一个新的greenlet 并切换到它,然后启动reactor。
from greenlet import greenlet
def main():
greenlet(run).switch()
reactor.run()
当run 开始它的第一个异步操作时,它切换回反应器greenlet(在这种情况下是“主”greenlet,但它并不重要)以让异步操作完成。完成后,corotwine 将回调转换为 greenlet 切换回run。所以run 被授予了直接运行的错觉,就像一个“正常”的同步程序。不过请记住,这只是一种错觉。
因此,您可以尽可能远离 Twisted 最常用的面向回调的样式。不过,这不一定是个好主意。