编写一个阻塞包装器用于Twisted的IRC客户端
我正在尝试为一个IRC库写一个非常简单的接口,像这样:
import simpleirc
connection = simpleirc.Connect('irc.freenode.net', 6667)
channel = connection.join('foo')
find_command = re.compile(r'google ([a-z]+)').findall
for msg in channel:
for t in find_command(msg):
channel.say("http://google.com/search?q=%s" % t)
根据他们的示例,我遇到了一些麻烦(代码有点长,所以我把它粘贴在这里)。因为在调用回调函数<IRCClient 实例>.privmsg
时,需要返回channel.__next__
,所以似乎没有一个干净的解决方案。使用异常或线程在这里感觉不太合适,有没有更简单(阻塞?)的方式来使用twisted,使这成为可能?
1 个回答
一般来说,如果你试图以“阻塞”的方式使用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。当然,yield
表达式仍然存在,我不知道这会让你有多不爽。;)
你甚至可以进一步远离回调,让异步操作所需的上下文切换完全不可见。这就像你家外面的步道上满是看不见的熊陷阱一样糟糕。不过,这确实是可能的。使用类似 corotwine 的东西,它本身基于一个第三方的协程库,你可以让 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
函数创建一个新的绿色线程,并切换到它,然后启动反应器。
from greenlet import greenlet
def main():
greenlet(run).switch()
reactor.run()
当 run
到达它的第一个异步操作时,它会切换回反应器的绿色线程(在这种情况下,这是“主”绿色线程,但这并不重要),以便让异步操作完成。当它完成时,corotwine 会将回调转换为切换回 run
的绿色线程。因此,run
看起来就像是正常的同步程序一样,顺利运行。请记住,这只是一种幻觉。
所以,你可以尽可能远离Twisted常用的回调风格。不过,这不一定是个好主意。