Welcome, guest | Sign In | My Account | Store | Cart

IMPROVED. The concept of events is heavily used in GUI libraries and is the foundation for most implementations of the MVC (Model, View, Controller) design pattern (the latter being my prime motivation for this recipe). Another prominent use of events is in communication protocol stacks, where lower protocol layers need to inform upper layers of incoming data and the like. Here is a handy class that encapsulates the core to event subscription and event firing and feels like a "natural" part of the language.

Python, 82 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
######################################################################
## 
## Events
## 
######################################################################

class Events:
   def __getattr__(self, name):
      if hasattr(self.__class__, '__events__'):
         assert name in self.__class__.__events__, \
                "Event '%s' is not declared" % name
      self.__dict__[name] = ev = _EventSlot(name)
      return ev
   def __repr__(self): return 'Events' + str(list(self))
   __str__ = __repr__
   def __len__(self): return NotImplemented
   def __iter__(self):
      def gen(dictitems=self.__dict__.items()):
         for attr, val in dictitems:
            if isinstance(val, _EventSlot):
               yield val
      return gen()

class _EventSlot:
   def __init__(self, name):
      self.targets = []
      self.__name__ = name
   def __repr__(self):
      return 'event ' + self.__name__
   def __call__(self, *a, **kw):
      for f in self.targets: f(*a, **kw)
   def __iadd__(self, f):
      self.targets.append(f)
      return self
   def __isub__(self, f):
      while f in self.targets: self.targets.remove(f)
      return self

######################################################################
## 
## Demo
## 
######################################################################

if __name__ == '__main__':

   class MyEvents(Events):
      __events__ = ('OnChange', )

   class ValueModel(object):
      def __init__(self):
         self.events = MyEvents()
         self.__value = None
      def __set(self, value):
         if (self.__value == value): return
         self.__value = value
         self.events.OnChange()
         ##self.events.OnChange2() # would fail
      def __get(self):
         return self.__value
      Value = property(__get, __set, None, 'The actual value')

   class SillyView(object):
      def __init__(self, model):
         self.model = model
         model.events.OnChange += self.DisplayValue
         ##model.events.OnChange2 += self.DisplayValue # would raise exeception
      def DisplayValue(self):
         print self.model.Value


   model = ValueModel()
   view = SillyView(model)

   print '\n--- Events Demo ---'
   # Events in action
   for i in range(5):
      model.Value = 2*i + 1
   # Events introspection
   print model.events
   for event in model.events:
      print event

The C# language provides a handy way to declare, subscribe to and fire events. I wanted something similar in python, and this recipe is the result.

Technically, an event is a "slot" where callback functions (event handlers) can be attached to - a process referred to as subscribing to an event. To subscribe to an event:

events = Events()
...
events.OnChange += MyEventHandlerForTheOnChangeEvent

When the event is fired, all attached event handlers are invoked in sequence. To fire the event, perform a call on the slot:

events.OnChange()

Usually, instances of Events will not hang around loosely like above, but will typically be embedded in model objects, like here:

class MyModel(object):
   def __init__(self):
      self.events = Events()
      ...

Similarly, view and controller objects will be the prime event subscribers:

class MyModelView(SomeWidget):
   def __init__(self, model):
      ...
      self.model = model
      model.events.OnChange += self.DisplayValue
      ...
   def DisplayValue(self):
      ...

The Events and _EventSlot classes provide some introspection support, too:

  • Every event (aka event slot) has a __name__ attribute
  • You can iterate through all registered events in Events instances

This is usefull for ex. for an atomatic event subscription based on method name patterns.

Note that the recipe does not check if an event that is being subscribed to can actually be fired, unless the class attribute __events__ is defined. This can cause a problem if an event name is slightly misspelled. If this is an issue, subclass Events and list the possible events, like:

class MyEvents(Events):
   __events__ = ('OnThis', 'OnThat', ...)

Cheers and happy event handling!

See Also
  • Recipe 52289, "Multicasting on objects", by Eduard Hiti shows a simple Multicast implementation (renamed to Multiplex in the 1st edition of the Cookbook).
  • This recipe differs from Multiplex in that arbitrarily named event handlers are bound to an arbitrarily named event, whereas Mutliplex dispatches a call of the form multiplex.f() to the method f in every object in the Multiplex.

5 comments

John Vasquez 18 years, 11 months ago  # | flag

Nice and practical. Nice and practical, easy to use recipe.

Alexander Semenov 18 years, 11 months ago  # | flag

It is may be simpler. Why class Events and it's derivatives with all of this magic is needed? Only to steal event's name and pass it to _EventSlot constructor? I think _EventSlot (better renamed to 'event') is enough in most cases:

class MyModel(object):
   def __init__(self):
      self.OnChange = event('OnChange')
      ...
   def __set(self, value):
      ...
      self.OnChange()

class MyModelView(SomeWidget):
   def __init__(self, model):
      ...
      self.model = model
      model.OnChange += self.DisplayValue
      ...
   def DisplayValue(self):
      ...
Alexander Semenov 18 years, 10 months ago  # | flag

weakevents. I think it's interesting to add support of "weak events" (weak references to handlers) so handlers may die when not needed anymore

Zoran Isailovski (author) 18 years, 10 months ago  # | flag

Simpler implementation here means complication for client code. The class Events is there mainly for 3 reasons:

  1. Events (Slots) are added automatically, so there is no need to declare/create them separately. This is great for prototyping. (Note that __events__ is optional and should primarilly help detect misspelled event names.)

  2. To provide (and encapsulate) some level of introspection.

  3. And yes, to "steel the name" and hereby remove unneeded redundancy in a call like

    xxx.OnChange = event('OnChange')

Zoran Isailovski (author) 18 years, 10 months ago  # | flag

Not really. I did implement weak events for fun, and they are not helpful at all.

If you think further, adding weak event handlers would only prevent observers from beeing kept alive by their subject alone. But, since observers are usually GUI elements, their life is determined by user actions, not their reference count. When the user closes a window, for ex., the underlying widget/handle/whatever is destroyed, even if the wrapping python object continues to exist. Now, whether there are references to the python object or not - it becomes practically unusable.

So what you actually need is to unsubscribe from the subject whenever the underlying widget goes away, but no weakrefs will help you here.

Created by Zoran Isailovski on Sun, 24 Apr 2005 (PSF)
Python recipes (4591)
Zoran Isailovski's recipes (13)

Required Modules

  • (none specified)

Other Information and Tasks