我正在尝试为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)
在their example工作,我遇到了麻烦(代码有点冗长,所以我粘贴了here)。由于在调用回调channel.__next__
时需要返回对<IRCClient instance>.privmsg
的调用,因此似乎没有干净的选项。在这里使用异常或线程似乎是错误的,是否有更简单的(阻塞?)方式使用twisted会使这成为可能?
答案 0 :(得分:10)
一般来说,如果你试图以“阻塞”的方式使用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
替换。如果您的Googler
版本具有不同的API(上面的那个应该与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并切换到它,然后启动反应器。
from greenlet import greenlet
def main():
greenlet(run).switch()
reactor.run()
当run
进入第一个异步操作时,它会切换回reactor greenlet(在这种情况下是“主”greenlet,但这并不重要)让异步操作完成。完成后,corotwine将回调转换为greenlet切换回run
。因此,run
被赋予直接运行的幻觉,就像“正常”的同步程序一样。但请记住,这只是一种幻觉。
因此,可以根据需要远离最常用于Twisted的回调导向样式。不过,这不一定是个好主意。