emptypage.jp > Notes

Event-driven programming in Python

March, 2011
Original: 日本語 (Japanese)

The event mechanism in C# is nice. Some people implement the same mechanism in their favorite languages other than C#. You can find a lot of event implementations in Python too; see search results for “c# event python” in Google for instance. This article shows yet another implementation of event mechanism in Python, which is a little bit improved by using an interesting feature of Python, descriptor.

Prerequisites

(TODO: Maybe I should explain the C#’s event mechanism and show how wonderful it is. I, however, don't have much time to do that now.)

Simpler implementation

Let's start with a simple implementation:

The last 3-line-code enables you to write your code like e += handler, e -= handler and e(earg) instead of e.add(handler), e.remove(handler) and e.fire(earg). See Python document about these special methods.

A script which uses this module will be like this:

When you run this script, you’ll get:

C:\...>python event_sample.py
foo!

That’s OK. How about that? If you are sure that they understand what event mechanism is and how to use our module, that’s totally OK. However, We can point some problems out abount this:

  1. It is hard for users of Publisher to know what kind of event does Publisher fire (and even whether it fires some kinds of events), because all these event stuff is prepered in the __init__ method.
  2. You have to keep in mind that you should give self as the first argument in calling event handlers, like self.evt_foo(self).

Better documentation may help you though, let’s think more efficient way.

You may pass the sender objects of the events in __init__, to fire events like self.evt_foo(). However, this approach needs codes like self.evt_foo = eventEvent(self) in __init__, so only moves points of the problem.

Another approach: you design a class (e.g. that is called EventPublisher) with a mechanism that fires event, and ask users to subclass it. However, it is not recommended that forcing subclassing upon them. We prefer casual way.

Imagine defining events of a class as just like a methods or properties of that. Is it impossible unless revising the specifications of Python? Well, no, it is possible; think how they implement the Python's property mechanism...

Improved implementation

Here’s the implementation that avoids the matters above.

Usage

Before event.py, Let’s look at the sample script code which uses it. You will find that the event-related codes are written more declaratively and naturally there:

Look at the Publisher class. Now events are members of the class (not of instance). You can easily know what kind of events this class publishes with this way. And you can type less. Aregument of event.Event contains document about the event itself. They are used when user do help(Publisher).

Publisher.evt_foo event should be published when foo method runs, and the code corresponds to this is self.evt_foo(). Calling class member object like a method: event handlers are still called with the Publisher instance as the first argument (Trust me). We should write publisher object down explicitly in the simpler implementation but we are free from that now. self.evt_foo() may called with an argument such as self.evt_foo(earg). Event handler function shoud accept two arguments: the publisher object (sender) and the event-defined custom value (earg). None will be passed to earg when you fires event without argument like self.evt_foo().

I think the improved version looks more suitable for our purpose, doesn’t it?

Result (same as that of the simpler one):

C:\...>python event_sample.py
foo!

Code

Here is the code of our improved event.py. More lines but still less than 100.

You may find some unfamiliar special method names.

Details

The event objects in the sample script are called like methods, without caller object itself as an argument. How that can be done? Let’s launch Python interpreter and watch behaviour of event.py for a while:

C:\...>python
Python 2.6.5 (r265:79096, Mar 19 2010, 21:48:26) [MSC v.1500 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import event
>>> class C(object):
...   evt = event.Event('test')
...
>>> C.evt
<event.Event object at 0x00AF4A70>
>>> c = C()
>>> c
<__main__.C object at 0x00AF4A30>
>>> c.evt
<event.EventHandler object at 0x00AF4950>
>>> 

You see class member C.evt and instance member c.evt are not the same object (see the IDs of each object are different). We got an event.EventHandler instance in the later case. The role of this object is to bind the instance object and the event object of the class. Every event.EventHandler instance has informations about the instances and the event object. You can see that by doing like this:

>>> c.evt.obj
<__main__.C object at 0x00AF4A30>
>>> c.evt.event
<event.Event object at 0x00AF4A70>
>>>

This is achived by a feature of Python language called descriptor. For instance, you instantiate Foo class and set it as a member of x as x.foo = Foo(). When the Foo instance object is evaluated, is there any way for x.foo to know x, the object which contains itself? Descriptor is the way. In other words, it is the mechanism that enables object to know its context on the code. In the output above, C.evt detects whether its owner is a class object (C) or a instance object (c) and returns different result depending on it.

Our new Event class has a method called __get__. It is a special method for descriptor. When C.evt is evaluated, this method is called as self.__get__(None, C). And when c.evt is evaluated, this is called like self.__get__(c, C). On our code, we return event.Event instance itself when the first argument of the method is None, while return EventHandler(self, obj) instance when it is a class instance object.

Standard functions such as property, classmethod and staticmethod are also implemented with using descriptor (they are implemented in C but its inside is the same).

See references below about the details of the descriptor.

Conclusion

As to implementing event mechanism in Python, it is hard with simple approaches to sweep redundancy of the code such as self.evt.fire(self) or self.evt = event.Event(self). They will require two “self”s for the caller and the argument or the right value and the left value in the same line. Descriptor prvides the solution. The event implementation with using descriptor is easy to use and free from any redundant coding restriction. I think implementing event mechanism is one of the most suitable cases for the descriptor feature.

Descriptor is not an all-round player but it can be as effective as the specification of the Python language got extended depending on your idea.

References

Event implementations in Python

Descriptor

Terms

I thank all the developers and writers who wrote about implementations of event mechanism, descriptor and any other articles and reference codes.

Though I considered the correctness of this article, there may still remain errors or mistakes. I do not guarantee the correctness of the content. Comments are always welcomed. You can keep in touch with me in my guestbook page or e-mail below.

Creative Commons License CC BY 2.1 This article is licensed under the Creative Commons CC BY License. All the codes on the article are in the public domain.

History

2014-08-17
Put code snipets on Gist.
2011-03-08
English version (this article).
2010-04-16
Original (Japanese).

Copyright 2010-2011 Masaaki Shibata <mshibata at emptypage.jp>