Which Python packages offer a stand-alone event system?

Which Python packages offer a stand-alone event system?

PyPI packages

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

Theres more

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

Im 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 a
simple 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 Event
object (or handlers list). So at registration time the event already needs to exist.

Thats why the second style of event systems exists: the
publish-subscribe pattern.
Here, the handlers dont 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 is
determined 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. The
application contains fixed integration points (hooks), and each plugin may
connect to that hook and perform certain actions.

Other events

Note: threading.Event is not an event system
in the above sense. Its 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 wont get deleted
by the garbage collector. This can be desirable, but it can also lead to memory leaks:
the linked handlers are never
cleaned 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 Lennarts answer). Note: this example does not even support handler arguments.
  • LongPokes callable list implementation shows that such an event system can be implemented very minimalistically by subclassing list.
  • Felks variation EventHook also ensures the signatures of callees and callers.
  • spassigs EventHook (Michael Foords Event Pattern) is a straightforward implementation.
  • Josips 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 PHPs Symfony frameworks event-dispatcher.
  • dispatcher was extracted from django.dispatch but is getting fairly old.
  • Cristian Garcias 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.
  • Qts Signals and Slots are available from PyQt
    or 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 they
    only work in objects of classes that derive from QObject.

Ive 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 Ive seen, there is no auto generated pydoc for this, and no signatures, which really sucks.

Which Python packages offer a stand-alone event system?

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 event
theBroadcaster.onChange += myFunction

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

# fire event
theBroadcaster.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

Leave a Reply

Your email address will not be published.