【问题标题】:Writing a blocking wrapper around twisted's IRC client围绕 twisted 的 IRC 客户端编写阻塞包装器
【发布时间】:2010-04-22 01:21:08
【问题描述】:

我正在尝试为 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)。由于在调用回调<IRCClient instance>.privmsg 时需要返回对channel.__next__ 的调用,因此似乎没有一个干净的选项。在这里使用异常或线程似乎是错误的,有没有更简单的(阻塞?)使用扭曲的方法可以实现这一点?

【问题讨论】:

    标签: python asynchronous 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 之上实现此 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 最常用的面向回调的样式。不过,这不一定是个好主意。

    【讨论】:

    • 很好,感谢您的广泛帖子!我似乎无法让最后一个示例正常工作 - 得到“AssertionError:不要从反应堆 greenlet 运行 gConnectTCP”。我是不是做错了什么?
    • 我想我对倒数第三个代码引用感到困惑:应该从哪里调用 run() 函数?
    • 我添加了另一个小代码 sn-p 和一些试图解释它的文本。我希望这可以解决问题。
    • 成功了,谢谢! greenlet 和twisted 都是新手,因此非常感谢手持。再次感谢!
    猜你喜欢
    • 1970-01-01
    • 2015-01-05
    • 1970-01-01
    • 1970-01-01
    • 2015-06-15
    • 1970-01-01
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    相关资源
    最近更新 更多