Написание блокирующей оболочки вокруг IRC-клиента twisted - PullRequest
3 голосов
/ 22 апреля 2010

Я пытаюсь написать очень простой интерфейс для библиотеки 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)

Работая с их примера , я столкнулся с проблемой (код немного длинный, поэтому я вставил его здесь ). Так как вызов channel.__next__ должен быть возвращен при вызове обратного вызова <IRCClient instance>.privmsg, кажется, что нет чистой опции. Использование исключений или потоков кажется неправильным, есть ли более простой (блокирующий?) Способ использования витой, который сделал бы это возможным?

1 Ответ

10 голосов
/ 22 апреля 2010

В общем, если вы пытаетесь использовать 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 от Twisted, как написано - хотя я ее не тестировал). Для 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 , основанное на сторонней библиотеке сопрограмм для 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 и переключаетесь на него, а затем запускаете реактор.

from greenlet import greenlet

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

Когда run достигает своей первой асинхронной операции, он переключается обратно на реактивную гринлету (которая в данном случае является "главной" гринлетой, но это не имеет значения), чтобы позволить асинхронной операции завершиться. Когда это завершается, corotwine превращает обратный вызов в зеленый выключатель обратно в run. Так что run предоставляется иллюзия прохождения прямо, как "нормальная" синхронная программа. Имейте в виду, что это всего лишь иллюзия.

Таким образом, можно получить как можно дальше от стиля, ориентированного на обратный вызов, который чаще всего используется в Twisted. Это не всегда хорошая идея.

...