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



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



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

662   12  

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 сокеты/сигналы парадигмы, то же самое для django

Если я делаю асинхронный ввод-вывод, я использую собственный модуль выбора

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

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

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

Пы-сообщите пакетов 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!

Comments

    Ничего не найдено.