围绕 Twisted 的 IRC 客户端编写阻塞包装器

发布于 2024-08-30 04:08:31 字数 751 浏览 5 评论 0原文

我正在尝试为 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)

他们的例子,我遇到了麻烦(代码有点长,所以我粘贴了它此处)。由于在调用回调 .privmsg 时需要返回对 channel.__next__ 的调用,因此似乎没有一个干净的选项。使用异常或线程在这里似乎是错误的事情,是否有一种更简单(阻塞?)的使用扭曲的方法可以使这成为可能?

I'm trying to write a dead-simple interface for an IRC library, like so:

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)

Working from their example, I'm running into trouble (code is a bit lengthy, so I pasted it here). Since the call to channel.__next__ needs to be returned when the callback <IRCClient instance>.privmsg is called, there doesn't seem to be a clean option. Using exceptions or threads seems like the wrong thing here, is there a simpler (blocking?) way of using twisted that would make this possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

属性 2024-09-06 04:08:31

一般来说,如果您尝试以“阻塞”方式使用 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 的第三方协程库,您可以实现 < code>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 到达第一个异步操作时,它会切换回反应器 greenlet(在本例中是“主”greenlet,但这并不重要)以完成异步操作。完成后,corotwine 将回调转为 greenlet 开关,返回到 run。因此,run 被赋予了直接运行的错觉,就像“正常”同步程序一样。但请记住,这只是一种幻觉。

因此,您可以根据需要远离 Twisted 最常使用的面向回调的样式。但这不一定是个好主意。

In general, if you're trying to use Twisted in a "blocking" way, you're going to run into a lot of difficulties, because that's neither the way it's intended to be used, nor the way in which most people use it.

Going with the flow is generally a lot easier, and in this case, that means embracing callbacks. The callback-style solution to your question would look something like this:

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

This isn't absolutely required (but I strongly suggest you consider trying it). One alternative is 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()

Notice no more addCallbacks. It's been replaced by yield in a decorated generator function. This could get even closer to what you asked for if you had a version of Googler with a different API (the one above should work with IRCClient from Twisted as it is written - though I didn't test it). It would be entirely possible for Googler.join to return a Channel object of some sort, and for that Channel object to be iterable like this:

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

It's only a matter of implementing this API on top of the ones already present. Of course, the yield expressions are still there, and I don't know how much this will upset you. ;)

It's possible to go still further away from callbacks and make the context switches necessary for asynchronous operation to work completely invisible. This is bad for the same reason it would be bad for sidewalks outside your house to be littered with invisible bear traps. However, it's possible. Using something like corotwine, itself based on a third-party coroutine library for CPython, you can have the implementation of Channel do the context switching itself, rather than requiring the calling application code to do it. The result might look something like:

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

with an implementation of Channel that might look something like:

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

This in turn depends on a new Googler method, getNextMessage, which is a straightforward feature addition based on existing IRCClient callbacks:

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)

To run this, you create a new greenlet for the run function and switch to it, and then start the reactor.

from greenlet import greenlet

def main():
    greenlet(run).switch()
    reactor.run()

When run gets to its first asynchronous operation, it switches back to the reactor greenlet (which is the "main" greenlet in this case, but it doesn't really matter) to let the asynchronous operation complete. When it completes, corotwine turns the callback into a greenlet switch back into run. So run is granted the illusion of running straight through, like a "normal" synchronous program. Keep in mind that it is just an illusion, though.

So, it's possible to get as far away from the callback-oriented style that is most commonly used with Twisted as you want. It's not necessarily a good idea, though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文