在twisted的IRC客户端周围编写一个阻塞包装器

时间:2010-04-22 01:21:09

标签: python asynchronous twisted

我正在尝试为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会使这成为可能?

1 个答案:

答案 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的回调导向样式。不过,这不一定是个好主意。