代码之家  ›  专栏  ›  技术社区  ›  Andrey Fedorov

在twisted的irc客户端上编写一个阻塞包装器

  •  3
  • Andrey Fedorov  · 技术社区  · 14 年前

    我试图为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 回复  |  直到 14 年前
        1
  •  10
  •   Jean-Paul Calderone    14 年前

    一般来说,如果你试图用“屏蔽”的方式使用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 IRCClient 尽管我没有测试它)。完全有可能 Googler.join 归还 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的问题。当然了, 产量 表情还在,我不知道这会让你有多难过。;)

    可以进一步远离回调,使异步操作所需的上下文开关完全不可见。这是不好的,同样的原因,这将是不好的人行道以外,你的房子被乱扔看不见的捕熊器。不过,这是可能的。使用类似于 corotwine ,本身基于cpython的第三方协同程序库,您可以实现 通道 执行上下文切换本身,而不是要求调用应用程序代码来执行此操作。结果可能类似于:

    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,))
    

    实现了 通道 可能看起来像:

    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
    

    这又取决于 古格勒 方法, getNextMessage ,这是基于现有的 IrCclipse 回调:

    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()
    

    什么时候? 运行 进入第一个异步操作,它切换回reactor greenlet(在本例中是“主”greenlet,但这并不重要)以完成异步操作。当它完成时,corotwine将回调转换为greenlet开关 运行 . 所以 运行 就像一个“正常”的同步程序,被赋予了直接运行的错觉。不过,请记住,这只是一种幻觉。

    因此,可以尽量远离面向回调的样式,这种样式最常用于twisted。不过,这未必是个好主意。