编写一个阻塞包装器用于Twisted的IRC客户端

3 投票
1 回答
1259 浏览
提问于 2025-04-15 21:53

我正在尝试为一个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 个回答

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 替代了。如果你有一个不同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常用的回调风格。不过,这不一定是个好主意。

撰写回答