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.
(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.)
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:
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.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...
Here’s the implementation that avoids the matters above.
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!
Here is the code of our improved event.py. More lines but still less than 100.
You may find some unfamiliar special method names.
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.
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.
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.
This article is licensed under the Creative Commons CC BY License. All the codes on the article are in the public domain.
Copyright 2010-2011 Masaaki Shibata <mshibata at emptypage.jp>