Which Python Packages Offer a Stand-Alone Event System

Which Python packages offer a stand-alone event system?

PyPI packages

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

  • PyDispatcher 2.0.6: Aug 2022
  • blinker 1.5: Jun 2022
  • pymitter 0.4.0: June 2022
  • python-dispatch 0.2.0: Apr 2022
  • pluggy 1.0.0: August 2021
  • zope.event 4.5.0: Sept 2020
  • RxPy3 1.0.1: June 2020
  • Louie 2.0: Sept 2019
  • PyPubSub 4.0.3: Jan 2019
  • pyeventdispatcher 0.2.3a0: 2018
  • buslane 0.0.5: 2018
  • PyPyDispatcher 2.1.2: 2017
  • axel 0.0.7: 2016
  • dispatcher 1.0: 2012
  • py-notify 0.3.1: 2008

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.

How to implement events in Python?

Without fully analyzing the code in the question, I'd say you're in the right direction by using function callbacks. That's because Python, as far as I know, does not have a native implementation of events.

Some useful libraries or examples that build on this can be seen in articles such as the observer pattern, mimicking events or in answers to a related question.

This is the simplest code I can think of that illustrates the callback concept, without arguments:

   def on_notify():
print("OK, I'm up-to-date")

def do_something(update):
# do whatever I need to do
print("I've changed\n")
update()

do_something(on_notify)

Which outputs:

I've changed

OK, I'm up-to-date

Our "worker" function takes a function parameter to be called when an event happens. In this case it's only one, but a list could be used, so that we have many watchers, which is what the other more complete examples do.

Also relevant are event objects, a mechanism for objects to communicate between threads, which is something worth considering for user interfaces. My guess is that most libraries and frameworks out there which implement the "missing events" functionality are based on either or both of these core methods.

Monitoring system with events in Python

You can do most of what you want with forty lines of Python code. This is my own design that I use all the time. The function names are chosen to make it a drop-in replacement for Qt's "signals" and "slots".

It's simple to use. You create a PSignal. You register handlers by calling the connect method. A handler can be any callable. When an event occurs you emit a signal (i.e., notify an event) by calling the emit function. Every registered callable runs at that point. The object calling emit doesn't know, or care, whether anyone is listening or what happens if they are.

You can also disconnect a handler.

There is a lot of debugging code because I discovered that otherwise certain errors can be difficult to track down.

In your question you wanted each handler to be a monitor, and in my design handlers are just functions. But it seems to me that your "monitor" concept is independent of the event/handler mechanism. You're going to have to write functions to make your application go, and it should be pretty easy to make those functions call your monitors.

The code is extensively tested with Python 3.3.

#! python3
import traceback

class PSignal:
def __init__(self, debug=False):
self.debug = debug
self.__handlers = []

def clear(self):
"""Deletes all the handlers."""
self.__handlers.clear()

def connect(self, f):
"""f is a python function."""
if not callable(f):
raise ValueError("Object {!r} is not callable".format(f))
self.__handlers.append(f)
if self.debug:
print("PSIGNAL: Connecting", f, self.__handlers)

def disconnect(self, f):
for f1 in self.__handlers:
if f == f1:
self.__handlers.remove(f)
return

def emit(self, *x, **y):
self._emit(*x, **y)

def check_debug(self):
if self.debug and self.__handlers:
print("PSIGNAL: Signal emitted")
traceback.print_stack()

def _emit(self, *x, **y):
self.check_debug()
for f in self.__handlers:
try:
if self.debug:
print("PSIGNAL: emit", f, len(x), x, y)
f(*x, **y)
except Exception:
print("PSIGNAL: Error in signal", f)
traceback.print_exc()

Does Python classes support events like other languages?

Python doesn't have any sort of event system built-in, but it's could be implemented pretty simply. For example:

class ObjectWithEvents(object):
callbacks = None

def on(self, event_name, callback):
if self.callbacks is None:
self.callbacks = {}

if event_name not in self.callbacks:
self.callbacks[event_name] = [callback]
else:
self.callbacks[event_name].append(callback)

def trigger(self, event_name):
if self.callbacks is not None and event_name in self.callbacks:
for callback in self.callbacks[event_name]:
callback(self)

class MyClass(ObjectWithEvents):
def __init__(self, contents):
self.contents = contents

def __str__(self):
return "MyClass containing " + repr(self.contents)

def echo(value): # because "print" isn't a function...
print value

o = MyClass("hello world")
o.on("example_event", echo)
o.on("example_event", echo)
o.trigger("example_event") # prints "MyClass containing \"Hello World\"" twice

Connections between different classes

You need to connect A and B somehow. An event system is an alternative, but if it's just a learning exercise we can do something simpler. For example, by saving references to the other class in each other, like this:

class A:
def __init__(self):
self.a = "a"
def set_other(self, other):
self.other = other
def print_a(self):
print("A says:", self.a)
self.other.print_warning()
def print_warning(self):
print("A says:", "Warning! B wrote something!")

class B:
def __init__(self):
self.b = "b"
def set_other(self, other):
self.other = other
def print_b(self):
print("B says:", self.b)
self.other.print_warning()
def print_warning(self):
print("B says:", "Warning! A wrote something!")

if __name__=="__main__":
class_a = A()
class_b = B()
class_a.set_other(class_b)
class_b.set_other(class_a)
class_a.print_a()
class_b.print_b()

I had to set the references after creating the instances, because we have a circular dependency. Also notice the correct way to declare attributes inside a class: self.a = "a", inside the __init__() method. It works as expected:

A says: a
B says: Warning! A wrote something!
B says: b
A says: Warning! B wrote something!

Notice that the calls to the other reference are encapsulated inside methods, you're not supposed to expose calls like other.other.other to the outside world. In the end, there MUST be someplace where you have references to the other class (or references to both classes), it's unavoidable.



Related Topics



Leave a reply



Submit