В общем, если вы пытаетесь использовать 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. Это не всегда хорошая идея.