This is a signals implementation for python. It is similar to the pydispatch module. This implementation enables you to create Signals as members of classes, as globals, or as locals. You may connect any number of functions or class methods to any signal. Connections manage themselves with the weakref module. Signals may also have arguments as long as all connected functions are callable with the same arguments.
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | """
File: signals.py
Author: Patrick Chasco
Created: July 26, 2005
Purpose: A signals implementation
"""
#========================================================
# Implementation
#========================================================
from weakref import *
import inspect
class Signal:
"""
class Signal
A simple implementation of the Signal/Slot pattern. To use, simply
create a Signal instance. The instance may be a member of a class,
a global, or a local; it makes no difference what scope it resides
within. Connect slots to the signal using the "connect()" method.
The slot may be a member of a class or a simple function. If the
slot is a member of a class, Signal will automatically detect when
the method's class instance has been deleted and remove it from
its list of connected slots.
"""
def __init__(self):
self.slots = []
# for keeping references to _WeakMethod_FuncHost objects.
# If we didn't, then the weak references would die for
# non-method slots that we've created.
self.funchost = []
def __call__(self, *args, **kwargs):
for i in range(len(self.slots)):
slot = self.slots[i]
if slot != None:
slot(*args, **kwargs)
else:
del self.slots[i]
def call(self, *args, **kwargs):
self.__call__(*args, **kwargs)
def connect(self, slot):
self.disconnect(slot)
if inspect.ismethod(slot):
self.slots.append(WeakMethod(slot))
else:
o = _WeakMethod_FuncHost(slot)
self.slots.append(WeakMethod(o.func))
# we stick a copy in here just to keep the instance alive
self.funchost.append(o)
def disconnect(self, slot):
try:
for i in range(len(self.slots)):
wm = self.slots[i]
if inspect.ismethod(slot):
if wm.f == slot.im_func and wm.c() == slot.im_self:
del self.slots[i]
return
else:
if wm.c().hostedFunction == slot:
del self.slots[i]
return
except:
pass
def disconnectAll(self):
del self.slots
del self.funchost
self.slots = []
self.funchost = []
class _WeakMethod_FuncHost:
def __init__(self, func):
self.hostedFunction = func
def func(self, *args, **kwargs):
self.hostedFunction(*args, **kwargs)
# this class was generously donated by a poster on ASPN (aspn.activestate.com)
class WeakMethod:
def __init__(self, f):
self.f = f.im_func
self.c = ref(f.im_self)
def __call__(self, *args, **kwargs):
if self.c() == None : return
self.f(self.c(), *args, **kwargs)
#========================================================
# Example usage
#========================================================
if __name__ == "__main__":
class Button:
def __init__(self):
# Creating a signal as a member of a class
self.sigClick = Signal()
class Listener:
# a sample method that will be connected to the signal
def onClick(self):
print "onClick ", repr(self)
# a sample function to connect to the signal
def listenFunction():
print "listenFunction"
# a function that accepts arguments
def listenWithArgs(text):
print "listenWithArgs: ", text
b = Button()
l = Listener()
# Demonstrating connecting and calling signals
print
print "should see one message"
b.sigClick.connect(l.onClick)
b.sigClick()
# Disconnecting all signals
print
print "should see no messages"
b.sigClick.disconnectAll()
b.sigClick()
# connecting multiple functions to a signal
print
print "should see two messages"
l2 = Listener()
b.sigClick.connect(l.onClick)
b.sigClick.connect(l2.onClick)
b.sigClick()
# disconnecting individual functions
print
print "should see two messages"
b.sigClick.disconnect(l.onClick)
b.sigClick.connect(listenFunction)
b.sigClick()
# signals disconnecting automatically
print
print "should see one message"
b.sigClick.disconnectAll()
b.sigClick.connect(l.onClick)
b.sigClick.connect(l2.onClick)
del l2
b.sigClick()
# example with arguments and a local signal
print
print "should see one message"
sig = Signal()
sig.connect(listenWithArgs)
sig("Hello, World!")
|
I really liked the C++ implementation of the Signal/Slot pattern by Sarah Thompson that can be found at http://sigslot.sourceforge.com/. My goal here was to create a similar implementation in python.
Utilizing this implementation, you need only create an instance of Signal. The instance could be a member of a class, a global, or a local variable. It doesn't matter. There is no explicit slot implementation, as a Signal accepts any function or instance method.
Signal.connect(func) or Signal.connect(object.method)
Weakrefs are used, thus the Signal itself will never keep a connected object alive. When connected objects are deleted for any reason the Signal will remove that object's connections automatically.
Signals implement the __call__ method. In order to fire a signal to all connected functions, simply invoke the signal:
sig = Signal() sig.connect(function) sig()
It is also possible to call signals with arguments, as long as all the connected functions are callable with those same arguments:
def function(a, b): print a, b
sig = Signal() sig.connect(function) sig("hello, ", "world!")
Advantages...? What's the advantage of this over PyDispatcher?
Signal and Borg. Connecting a Borg method to a Signal does not work if no Borg instance is kept (which is the aim of the Borg).
The solution is to use a 'staticmethod'. No Borg instance will be given to the method, but as it is a Borg, it's not a problem.
Here is an example:
Result:
onClick: val = 2
What's a borg method?
Borg method. See this recipe about Borg/Singleton: