Which Python packages offer a stand-alone event system?

299,664

Solution 1

PyPI packages

As of January 2022, 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 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.

That's why the second style of event systems exists: the publish-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 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. 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 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 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 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.

Solution 2

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.

Solution 3

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

Solution 4

I use zope.event. It's the most bare bones you can imagine. :-) In fact, here is the complete source code:

subscribers = []

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

Note that you can't send messages between processes, for example. It's not a messaging system, just an event system, nothing more, nothing less.

Solution 5

I found this small script on Valued Lessons. It seems to have just the right simplicity/power ratio I'm after. Peter Thatcher is the author of following code (no licensing is mentioned).

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()
Share:
299,664

Related videos on Youtube

Josip
Author by

Josip

Updated on January 28, 2022

Comments

  • Josip
    Josip over 2 years

    I am aware of pydispatcher, but there must be other event-related packages around for Python.

    Which libraries are available?

    I'm not interested in event managers that are part of large frameworks, I'd rather use a small bare-bones solution that I can easily extend.

  • Lennart Regebro
    Lennart Regebro almost 15 years
    You can send whatever you want with zope.event. But my point is that it's not a proper messaging system, as you can't send events/messages to other processes or other computers. You should probably be a but more specific with your requirements.
  • Rudy Lattae
    Rudy Lattae over 13 years
    I find this style rather intriguing. It's sweetly bare-bones. I like the fact that it allows one to manipulate events and their subscribers as autonomous operations. I'll see how it fares in a real project.
  • Jonathan Livni
    Jonathan Livni almost 13 years
    A drawback of using this is that you need to first add an event before you register as a subscriber. If only the publishers add their events (not a must, just a good practice), then you must initialize the publishers before the subscribers which is a pain in large projects
  • akaRem
    akaRem over 11 years
    Very beautiful minimalistic style! super!
  • Simon Bergot
    Simon Bergot about 11 years
    the last method is bugged because self.__handlers is modified during the iterations. Fix: ` self.__handlers = [h for h in self.__handlers if h.im_self != obj]`
  • florisla
    florisla about 11 years
    Using a set() instead of a list is nice to avoid handlers being registered twice. One consequence is that the handlers are not being called in the order they were registered. Not necessarily a bad thing though...
  • Admin
    Admin about 10 years
    I can't upvote this enough, this is really straightforward and easy.
  • Eric Marcos
    Eric Marcos about 9 years
    @Simon is right, but introduces a bug because we can have unbound functions in self.__handlers. Fix: self.__handlers = [h for h in self._handlers if getattr(h, 'im_self', False) != obj]
  • omgimdrunk
    omgimdrunk over 6 years
    big favor, could someone explain this like i was 10? Does this class get inherited by the main class? I don't see a init so super() wouldn't be used. It's not clicking for me for some reason.
  • Gabe Spradlin
    Gabe Spradlin over 6 years
    @omgimdrunk A simple event handler would fire off one or more callable functions whenever an event fired. A class to "manage" this for you would require the following methods at a minimum - add & fire. Within that class you would need to maintain a list of handlers to be executed. Let's put that in the instance variable _bag_of_handlers which is a list. The class's add method would simply be self._bag_of_handlers.append(some_callable). The class's fire method would loop thru` _bag_of_handlers` passing the provided args and kwargs to the handlers and execute each in sequence.
  • Gabe Spradlin
    Gabe Spradlin over 6 years
    @omgimdrunk This code sits on top of the list object. Instead of the add method you just use append like you would for any list. Instead of the fire method this Event class uses the` __call__` magic method.
  • Robino
    Robino about 6 years
    @florisla could swap out for OrderedSet, if one so desired.
  • David Parks
    David Parks about 5 years
    The home page is out of commission for this one, perhaps not being supported any longer?
  • Atreyagaurav
    Atreyagaurav about 4 years
    I like your design, it's minimalistic and easy to understand. and it'd be lightweight by not having to import some modules.
  • RedKnight91
    RedKnight91 over 2 years
    This technique prevents instances to be GC'ed if a method of theirs is used as a callback and not explicitly removed from the event's list
  • Mahmood Dehghan
    Mahmood Dehghan about 2 years
    Since I'm working with Django, I tried django.dispatch, and it works fine. Thanks.