Event system in Python [closed] Event system in Python [closed] python python

Event system in Python [closed]


PyPI packages

As of January 2021, these are the event-related packages available on PyPI,ordered by most recent release date.

There's more

That's a lot of libraries to choose from, using very different terminology (events, signals, handlers, method dispatch, hooks, ...).

I'm trying to keep an overview of the above packages, plus the techniques mentioned in the answers here.

First, some terminology...

Observer pattern

The most basic style of event system is the 'bag of handler methods', which is asimple implementation of the Observer pattern.

Basically, the handler methods (callables) are stored in an array and are each called when the event 'fires'.

Publish-Subscribe

The disadvantage of Observer event systems is that you can only register the handlers on the actual Eventobject (or handlers list). So at registration time the event already needs to exist.

That's why the second style of event systems exists: thepublish-subscribe pattern.Here, the handlers don't register on an event object (or handler list), but on a central dispatcher.Also the notifiers only talk to the dispatcher. What to listen for, or what to publish isdetermined by 'signal', which is nothing more than a name (string).

Mediator pattern

Might be of interest as well: the Mediator pattern.

Hooks

A 'hook' system is usally used in the context of application plugins. Theapplication contains fixed integration points (hooks), and each plugin mayconnect to that hook and perform certain actions.

Other 'events'

Note: threading.Event is not an 'event system'in the above sense. It's a thread synchronization system where one thread waits until another thread 'signals' the Event object.

Network messaging libraries often use the term 'events' too; sometimes these are similar in concept; sometimes not.They can of course traverse thread-, process- and computer boundaries. See e.g.pyzmq, pymq,Twisted, Tornado, gevent, eventlet.

Weak references

In Python, holding a reference to a method or object ensures that it won't get deletedby the garbage collector. This can be desirable, but it can also lead to memory leaks:the linked handlers are nevercleaned up.

Some event systems use weak references instead of regular ones to solve this.

Some words about the various libraries

Observer-style event systems:

  • zope.event shows the bare bones of how this works (see Lennart's answer). Note: this example does not even support handler arguments.
  • LongPoke's 'callable list' implementation shows that such an event system can be implemented very minimalistically by subclassing list.
  • Felk's variation EventHook also ensures the signatures of callees and callers.
  • spassig's EventHook (Michael Foord's Event Pattern) is a straightforward implementation.
  • Josip's Valued Lessons Event class is basically the same, but uses a set instead of a list to store the bag, and implements __call__ which are both reasonable additions.
  • PyNotify is similar in concept and also provides additional concepts of variables and conditions ('variable changed event'). Homepage is not functional.
  • axel is basically a bag-of-handlers with more features related to threading, error handling, ...
  • python-dispatch requires the even source classes to derive from pydispatch.Dispatcher.
  • buslane is class-based, supports single- or multiple handlers and facilitates extensive type hints.
  • Pithikos' Observer/Event is a lightweight design.

Publish-subscribe libraries:

  • blinker has some nifty features such as automatic disconnection and filtering based on sender.
  • PyPubSub is a stable package, and promises "advanced features that facilitate debugging and maintaining topics and messages".
  • pymitter is a Python port of Node.js EventEmitter2 and offers namespaces, wildcards and TTL.
  • PyDispatcher seems to emphasize flexibility with regards to many-to-many publication etc. Supports weak references.
  • louie is a reworked PyDispatcher and should work "in a wide variety of contexts".
  • pypydispatcher is based on (you guessed it...) PyDispatcher and also works in PyPy.
  • django.dispatch is a rewritten PyDispatcher "with a more limited interface, but higher performance".
  • pyeventdispatcher is based on PHP's Symfony framework's event-dispatcher.
  • dispatcher was extracted from django.dispatch but is getting fairly old.
  • Cristian Garcia's EventManger is a really short implementation.

Others:

  • pluggy contains a hook system which is used by pytest plugins.
  • RxPy3 implements the Observable pattern and allows merging events, retry etc.
  • Qt's Signals and Slots are available from PyQtor PySide2. They work as callback when used in the same thread,or as events (using an event loop) between two different threads. Signals and Slots have the limitation that theyonly work in objects of classes that derive from QObject.


I've been doing it this way:

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)

However, like with everything else I've seen, there is no auto generated pydoc for this, and no signatures, which really sucks.


We use an EventHook as suggested from Michael Foord in his Event Pattern:

Just add EventHooks to your classes with:

class MyBroadcaster()    def __init__():        self.onChange = EventHook()theBroadcaster = MyBroadcaster()# add a listener to the eventtheBroadcaster.onChange += myFunction# remove listener from the eventtheBroadcaster.onChange -= myFunction# fire eventtheBroadcaster.onChange.fire()

We add the functionality to remove all listener from an object to Michaels class and ended up with this:

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