Система событий в Python

какую систему событий для Python вы используете? Я уже знаю о pydispatcher, но мне было интересно, что еще можно найти или обычно используется?

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

12 ответов


завершение различных систем событий, которые упоминаются в ответах здесь:

самым основным стилем системы событий является "мешок методов обработчика", который является простой реализацией шаблон Observer. В принципе, методы обработчика (вызываемые) хранятся в массиве и вызываются каждый раз, когда событие "срабатывает".

  • zope.событие показывает голые кости, как это работает (см. ответ Леннарта). Примечание: этот пример даже не поддерживает аргументы обработчика.
  • LongPoke по отзывной список' реализация показывает, что такая система событий может быть реализована очень минималистично путем подклассов list.
  • EventHook спассига (шаблон событий Майкл Фурд) является простой реализацией.
  • класс событий ценных уроков Иосипа в основном то же самое, но использует set вместо list для хранения сумки и инструментов __call__ которые являются разумными дополнениями.
  • PyNotify аналогичен по концепции, а также предоставляет дополнительные понятия переменных и условий ("переменное измененное событие").
  • Аксель в основном сумка-оф-обработчики с большим количеством функций, связанных с резьбой, обработки ошибок,...

недостатком этих систем событий является то, что вы можете зарегистрировать обработчики только на фактическом Объект события (или список обработчиков). Поэтому во время регистрации событие уже должно существовать.

вот почему существует второй стиль систем событий:публикации-подписки шаблон. Здесь обработчики регистрируются не в объекте события (или списке обработчиков), а в Центральном диспетчере. Также уведомители разговаривают только с диспетчером. Что слушать, или что публиковать определяется "сигналом", который является не более чем именем (строка.)

  • поворотник есть некоторые классные функции, такие как автоматическое отключение и фильтрацию по отправителю.
  • PyPubSub на первый взгляд кажется довольно простой.
  • PyDispatcher кажется, что подчеркивает гибкость в отношении публикации "многие ко многим" и т. д.
  • Луи является переработанным PyDispatcher " предоставление инфраструктуры плагинов, включая Витой и PyQt конкретную поддержку". Похоже, он потерял техническое обслуживание после января 2016 года.
  • Джанго.отправка - это переписанный PyDispatcher "с более ограниченным интерфейсом, но более высокой производительностью".
  • сигналы и слоты Qt доступны из PyQt или PySide. Они работают как обратный вызов при использовании в одном потоке или как события (используя цикл событий) между двумя разными потоками. Сигналы и слоты имеют ограничение, что они только работа в объектах классов, производных от QObject.

Примечание: нарезание резьбы.Событие не является "системой событий" в вышеуказанном смысле. Это система синхронизации потоков, где один поток ждет, пока другой поток "сигнализирует" объект события.


Я делал это таким образом:

class Event(list):
    """Event subscription.

    A list of callable objects. Calling an instance of this will cause a
    call to each item in the list in ascending order by index.

    Example Usage:
    >>> def f(x):
    ...     print 'f(%s)' % x
    >>> def g(x):
    ...     print 'g(%s)' % x
    >>> e = Event()
    >>> e()
    >>> e.append(f)
    >>> e(123)
    f(123)
    >>> e.remove(f)
    >>> e()
    >>> e += (f, g)
    >>> e(10)
    f(10)
    g(10)
    >>> del e[0]
    >>> e(2)
    g(2)

    """
    def __call__(self, *args, **kwargs):
        for f in self:
            f(*args, **kwargs)

    def __repr__(self):
        return "Event(%s)" % list.__repr__(self)

однако, как и все остальное, что я видел, для этого нет автоматического pydoc и нет подписей, что действительно отстой.


мы используем EventHook, как было предложено Майклом Фордом в его Шаблон Событие:

просто добавьте EventHooks в свои классы с помощью:

class MyBroadcaster()
    def __init__():
        self.onChange = EventHook()

theBroadcaster = MyBroadcaster()

# add a listener to the event
theBroadcaster.onChange += myFunction

# remove listener from the event
theBroadcaster.onChange -= myFunction

# fire event
theBroadcaster.onChange.fire()

мы добавляем функциональность для удаления всех прослушивателей из объекта в класс Michaels и заканчиваем этим:

class EventHook(object):

    def __init__(self):
        self.__handlers = []

    def __iadd__(self, handler):
        self.__handlers.append(handler)
        return self

    def __isub__(self, handler):
        self.__handlers.remove(handler)
        return self

    def fire(self, *args, **keywargs):
        for handler in self.__handlers:
            handler(*args, **keywargs)

    def clearObjectHandlers(self, inObject):
        for theHandler in self.__handlers:
            if theHandler.im_self == inObject:
                self -= theHandler

Я использую zope.событие. Это самые голые кости, какие только можно себе представить. :-) На самом деле, вот полный исходный код:

subscribers = []

def notify(event):
    for subscriber in subscribers:
        subscriber(event)

обратите внимание, что вы не можете отправлять сообщения между процессами, например. Это не система обмена сообщениями, а просто система событий, ни больше, ни меньше.


Я нашел этот маленький скрипт на Стоимостью Уроки. Кажется, у него есть правильное соотношение простота/мощность, которое мне нужно. Питер Тэтчер является автором следующего кода (лицензирование не упоминается).

class Event:
    def __init__(self):
        self.handlers = set()

    def handle(self, handler):
        self.handlers.add(handler)
        return self

    def unhandle(self, handler):
        try:
            self.handlers.remove(handler)
        except:
            raise ValueError("Handler is not handling this event, so cannot unhandle it.")
        return self

    def fire(self, *args, **kargs):
        for handler in self.handlers:
            handler(*args, **kargs)

    def getHandlerCount(self):
        return len(self.handlers)

    __iadd__ = handle
    __isub__ = unhandle
    __call__ = fire
    __len__  = getHandlerCount

class MockFileWatcher:
    def __init__(self):
        self.fileChanged = Event()

    def watchFiles(self):
        source_path = "foo"
        self.fileChanged(source_path)

def log_file_change(source_path):
    print "%r changed." % (source_path,)

def log_file_change2(source_path):
    print "%r changed!" % (source_path,)

watcher              = MockFileWatcher()
watcher.fileChanged += log_file_change2
watcher.fileChanged += log_file_change
watcher.fileChanged -= log_file_change2
watcher.watchFiles()

вы можете посмотреть pymitter (pypi). Его небольшой однофайловый (~250 loc) подход "предоставление пространств имен, подстановочных знаков и TTL".

вот простой пример:

from pymitter import EventEmitter

ee = EventEmitter()

# decorator usage
@ee.on("myevent")
def handler1(arg):
   print "handler1 called with", arg

# callback usage
def handler2(arg):
    print "handler2 called with", arg
ee.on("myotherevent", handler2)

# emit
ee.emit("myevent", "foo")
# -> "handler1 called with foo"

ee.emit("myotherevent", "bar")
# -> "handler2 called with bar"

Я создал EventManager - класс (код в конце). Синтаксис следующий:

#Create an event with no listeners assigned to it
EventManager.addEvent( eventName = [] )

#Create an event with listeners assigned to it
EventManager.addEvent( eventName = [fun1, fun2,...] )

#Create any number event with listeners assigned to them
EventManager.addEvent( eventName1 = [e1fun1, e1fun2,...], eventName2 = [e2fun1, e2fun2,...], ... )

#Add or remove listener to an existing event
EventManager.eventName += extra_fun
EventManager.eventName -= removed_fun

#Delete an event
del EventManager.eventName

#Fire the event
EventManager.eventName()

вот пример:

def hello(name):
    print "Hello {}".format(name)

def greetings(name):
    print "Greetings {}".format(name)

EventManager.addEvent( salute = [greetings] )
EventManager.salute += hello

print "\nInitial salute"
EventManager.salute('Oscar')

print "\nNow remove greetings"
EventManager.salute -= greetings
EventManager.salute('Oscar')

выход:

первоначальный салют
Привет Оскар
Привет Оскар

теперь удалить поздравления
Привет Оскар

Код EventManger:

class EventManager:

    class Event:
        def __init__(self,functions):
            if type(functions) is not list:
                raise ValueError("functions parameter has to be a list")
            self.functions = functions

        def __iadd__(self,func):
            self.functions.append(func)
            return self

        def __isub__(self,func):
            self.functions.remove(func)
            return self

        def __call__(self,*args,**kvargs):
            for func in self.functions : func(*args,**kvargs)

    @classmethod
    def addEvent(cls,**kvargs):
        """
        addEvent( event1 = [f1,f2,...], event2 = [g1,g2,...], ... )
        creates events using **kvargs to create any number of events. Each event recieves a list of functions,
        where every function in the list recieves the same parameters.

        Example:

        def hello(): print "Hello ",
        def world(): print "World"

        EventManager.addEvent( salute = [hello] )
        EventManager.salute += world

        EventManager.salute()

        Output:
        Hello World
        """
        for key in kvargs.keys():
            if type(kvargs[key]) is not list:
                raise ValueError("value has to be a list")
            else:
                kvargs[key] = cls.Event(kvargs[key])

        cls.__dict__.update(kvargs)

Я сделал вариацию минималистичного подхода Longpoke, который также обеспечивает подписи как для абонентов, так и для абонентов:

class EventHook(object):
    '''
    A simple implementation of the Observer-Pattern.
    The user can specify an event signature upon inizializazion,
    defined by kwargs in the form of argumentname=class (e.g. id=int).
    The arguments' types are not checked in this implementation though.
    Callables with a fitting signature can be added with += or removed with -=.
    All listeners can be notified by calling the EventHook class with fitting
    arguments.

    >>> event = EventHook(id=int, data=dict)
    >>> event += lambda id, data: print("%d %s" % (id, data))
    >>> event(id=5, data={"foo": "bar"})
    5 {'foo': 'bar'}

    >>> event = EventHook(id=int)
    >>> event += lambda wrong_name: None
    Traceback (most recent call last):
        ...
    ValueError: Listener must have these arguments: (id=int)

    >>> event = EventHook(id=int)
    >>> event += lambda id: None
    >>> event(wrong_name=0)
    Traceback (most recent call last):
        ...
    ValueError: This EventHook must be called with these arguments: (id=int)
    '''
    def __init__(self, **signature):
        self._signature = signature
        self._argnames = set(signature.keys())
        self._handlers = []

    def _kwargs_str(self):
        return ", ".join(k+"="+v.__name__ for k, v in self._signature.items())

    def __iadd__(self, handler):
        params = inspect.signature(handler).parameters
        valid = True
        argnames = set(n for n in params.keys())
        if argnames != self._argnames:
            valid = False
        for p in params.values():
            if p.kind == p.VAR_KEYWORD:
                valid = True
                break
            if p.kind not in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY):
                valid = False
                break
        if not valid:
            raise ValueError("Listener must have these arguments: (%s)"
                             % self._kwargs_str())
        self._handlers.append(handler)
        return self

    def __isub__(self, handler):
        self._handlers.remove(handler)
        return self

    def __call__(self, *args, **kwargs):
        if args or set(kwargs.keys()) != self._argnames:
            raise ValueError("This EventHook must be called with these " +
                             "keyword arguments: (%s)" % self._kwargs_str())
        for handler in self._handlers[:]:
            handler(**kwargs)

    def __repr__(self):
        return "EventHook(%s)" % self._kwargs_str()

вот минимальный дизайн, который должен работать нормально. Что вам нужно сделать, так это просто наследовать Observer в классе, а затем использовать observe(event_name, callback_fn) для прослушивания определенного события. Всякий раз, когда это конкретное событие запускается в любом месте кода (т. е. Event('USB connected')), соответствующий обратный вызов будет срабатывать.

class Observer():
    _observers = []
    def __init__(self):
        self._observers.append(self)
        self._observed_events = []
    def observe(self, event_name, callback_fn):
        self._observed_events.append({'event_name' : event_name, 'callback_fn' : callback_fn})


class Event():
    def __init__(self, event_name, *callback_args):
        for observer in Observer._observers:
            for observable in observer._observed_events:
                if observable['event_name'] == event_name:
                    observable['callback_fn'](*callback_args)

пример:

class Room(Observer):
    def __init__(self):
        print("Room is ready.")
        Observer.__init__(self) # DON'T FORGET THIS
    def someone_arrived(self, who):
        print(who + " has arrived!")

# Observe for specific event
room = Room()
room.observe('someone arrived',  room.someone_arrived)

# Fire some events
Event('someone left',    'John')
Event('someone arrived', 'Lenard') # will output "Lenard has arrived!"
Event('someone Farted',  'Lenard')

Если я делаю код в pyQt, я использую парадигму Qt sockets/signals, то же самое для django

Если я делаю асинхронный ввод-вывод, я использую native select module

Если я usign синтаксический анализатор Sax python, я использую API событий, предоставляемый SAX. Таким образом, похоже, что я жертва базового API :-)

возможно, вы должны спросить себя, что вы ожидаете от Event framework/module. Мое личное предпочтение-использовать парадигму сокета / сигнала от QT. более подробную информацию об этом можно найти здесь


вот еще модуль для рассмотрения. Кажется жизнеспособным выбором для более требовательных применений.

Py-notify-это пакет Python предоставление инструментов для реализации Шаблон программирования наблюдателя. Эти инструменты включают сигналы, условия и переменная.

сигналы списков обработчики, вызывается при излучении сигнала. Условия в основном логические переменные, связанные с сигналом, который испускается при состояние изменения. Их можно совместить используя стандартные логические операции (не, и, так далее.) в сложных условиях. Переменные, в отличие от условий, могут содержать любой объект в Python, а не только логические, но их нельзя объединить.


Если вы хотите сделать более сложные вещи, такие как слияние событий или повторная попытка, вы можете использовать наблюдаемый шаблон и зрелую библиотеку, которая реализует это. https://github.com/ReactiveX/RxPY . Наблюдаемые очень распространены в Javascript и Java и очень удобны в использовании для некоторых асинхронных задач.

from rx import Observable, Observer


def push_five_strings(observer):
        observer.on_next("Alpha")
        observer.on_next("Beta")
        observer.on_next("Gamma")
        observer.on_next("Delta")
        observer.on_next("Epsilon")
        observer.on_completed()


class PrintObserver(Observer):

    def on_next(self, value):
        print("Received {0}".format(value))

    def on_completed(self):
        print("Done!")

    def on_error(self, error):
        print("Error Occurred: {0}".format(error))

source = Observable.create(push_five_strings)

source.subscribe(PrintObserver())

выход:

Received Alpha
Received Beta
Received Gamma
Received Delta
Received Epsilon
Done!