This is an dynamic dispatching approach inspired by C#-style events. The approach allows for the dispatch of an event to a series of chained methods through the use of a Dispatcher. The Dispatcher can be defined either as part of a class or merely as a variable in some code. When the Dispatcher is invoked the methods that are chained to it (i.e. handlers) are invoked. The dispatch can be either blocking or non-blocking, and is non-blocking by default.
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
# dispatch.py # definitions: import threading class Dispatcher(object): def __init__(self, targets=None, nonBlocking=True): if not targets or targets is None: self._targets =  else: self._targets = targets self._nonBlocking = nonBlocking def __iadd__(self, target): self._targets.append(target) return self def __isub__(self, target): self._targets.remove(target) return self def isNonBlocking(self): return self._nonBlocking nonBlocking = property(isNonBlocking) def __call__(self, *listArgs, **kwArgs): def invokeTargets(): for target in self._targets: target(*listArgs, **kwArgs) if self.nonBlocking: threading.Timer(0, invokeTargets).start() else: invokeTargets() # demos: def Test1(): """ A simple example demonstrating most functionality. """ def m1(): print 'm1 invoked' def m2(): print 'm2 invoked' e = Dispatcher() e += m1 e += m2 e += m2 print 'Dispatching:' e() e -= m1 print 'Dispatching:' e() e -= m2 print 'Dispatching:' e() def Test2(): """ A more realistic example for the OO programmer. """ class Sprite(object): def __init__(self, location): self._location = location locationChanged = Dispatcher() def getLocation(self): return self._location def setLocation(self, newLocation): oldLocation = self._location self._location = newLocation # Dispatch a "property change event" self.locationChanged(oldLocation, newLocation) location = property(getLocation, setLocation) s = Sprite((2,4)) def SpriteLocationChanged(oldLocation, newLocation): print 'oldLocation =', oldLocation print 'newLocation =', newLocation s.locationChanged += SpriteLocationChanged s.location = (3,4) s.location = (4,4) if __name__ == '__main__': Test1() Test2()
Having recently seen recipe http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/410686, I thought I would offer my approach.
Like the other recipe that I reference, mine is inspired by C#-style event handling. The difference between this and the C# approach is that I follow the python tradition of duck-typing where no explicit signatures are enforced. When you add a handler to the dispatch, it's up to you to keep the methods compatable with the arguments given in the dispatch. My recipe is similar to the C#-style events in that the handlers are non-blocking by default. That is, the calls to the handlers will not prevent the continuation of the code that issued the "dispatch".
This is different from the referenced recipe in that I think it is both simpler and more straightforward, in that it provides a simple implementation of event-style behavior that allows you to declare events where you want them without being forced to inherit, and handlers are easily added by client-code just as in C#. There are no required base-classes, no required class-slots, no required magic attributes. I'm choosing the simplicity of this approach over one that would require more forced introspection.