Signals/Slots support in Python
Scott SA
pydev at rscorp.ab.ca
Thu May 1 10:44:27 EDT 2008
On 5/1/08, Brian Vanderburg II (BrianVanderburg2 at aim.com) wrote:
>I don't know if any such support is already built in, so I ended up
>making my own simple signals/slots like mechanism. If anyone is
>interested then here it is, along with a simple test. It can connect to
>normal functions as well as instance methods. It also supports weak
>connections where when an object is gone, the slot is gone as well, the
>slot just holds a weak reference to the object.
Did you review this?
<http://pydispatcher.sourceforge.net/>
from what I understand is originally based upon this:
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/87056>
and subsequently integrated into this:
<http://djangoproject.com>
>-----
># Begin Signal
>import weakref
>import random
>
>class Signal:
> class Slot:
> def __init__(self, fn):
> self.__fn = fn
>
> def __call__(self, accum, *args, **kwargs):
> result = self.__fn(*args, **kwargs)
> return accum(result)
>
> class WeakSlot:
> def __init__(self, conn, parent, fn, obj):
> self.__conn = conn
> # Avoid circular references so deleting a signal will
> # allow deletion of the signal since the slot doesn't ref
> # back to it but only weakefs back to it
> self.__parent = weakref.ref(parent)
>
> self.__fn = fn
> self.__obj = weakref.ref(obj, self.Cleanup)
>
> def __call__(self, accum, *args, **kwargs):
> obj = self.__obj()
> if obj is None:
> return True
>
> result = self.__fn(obj, *args, **kwargs)
> return accum(result)
>
> def Cleanup(self, ref):
> parent = self.__parent()
> if parent is not None:
> parent.Disconnect(self.__conn)
>
> class Accumulator:
> def __call__(self, *args, **kwargs):
> return True
>
> def Finalize(self):
> return None
>
> def __init__(self):
> self.__slots = [ ]
>
> # This connects a signal to a slot, but stores a strong reference so
> # The object will not be deleted as long as the signal is connected
> def Connect(self, fn):
> conn = self.NewConn()
> self.__slots.append([conn, Signal.Slot(fn)])
> return conn
>
> # This connects a signal to a slot, but store a weak reference so
> # when the object is gone the slot will not be called. Because of
> # the implemenations, it is not possible to do WeakConnect(obj.Fn),
> # since obj.Fn is a new object and would go to 0 refcount soon after
> # the call to WeakConnect completes. Instead we must do a call as
> # WeakConnect(ObjClass.Fn, obj)
> # Only the object is weak-referenced. The function object is still
> # a normal reference, this ensures that as long as the object exists
> # the function will also exist. When the object dies, the slot will
> # be removed
> def WeakConnect(self, fn, obj):
> conn = self.NewConn()
> self.__slots.append([conn, Signal.WeakSlot(conn, self, fn, obj)])
> return conn
>
> # Disconnect a slot
> def Disconnect(self, conn):
> result = self.Find(conn)
> if result >= 0:
> del self.__slots[result]
>
> # Disconnect all slots
> def DisconnectAll(self):
> self.__slots = [ ]
>
> # Create an accumulator. Accumulator will be called as a callable
> # for each return value of the executed slots. Execution of slots
> # continues as long as the reutrn value of the accumulator call is
> # True. The 'Finalize'function will be called to get the result
> # A custom accumulator can be created by deriving from Signal and
> # Creating a custom 'Accumulator' class, or by deriving from Singal
> # and creating CreateAccumulator
> def CreateAccumulator(self):
> return self.Accumulator()
>
> # Execute the slots
> def __call__(self, *args, **kwargs):
> accum = self.CreateAccumulator()
> for conn in xrange(len(self.__slots)):
> if not self.__slots[conn][1](accum, *args, **kwargs):
> break
> return accum.Finalize()
>
> # Create a connection name
> def NewConn(self):
> value = 0
> while self.Find(value) >= 0:
> value = random.randint(1, 100000000)
> return value
>
> def Find(self, conn):
> for i in xrange(len(self.__slots)):
> if self.__slots[i][0] == conn:
> return i
>
> return -1
>
># End Signal
>
>def fn1():
> print "Hello World"
>
>def fn2():
> print "Goodbye Space"
>
>class O:
> def __init__(self, value):
> self.value = value
>
> def Action(self):
> print "O %d" % self.value
>
>a = Signal()
>
>a.Connect(fn1)
>a.Connect(fn2)
>
>print "Part 1"
>a()
>
>a.DisconnectAll()
>
>o1 = O(4)
>o2 = O(12)
>
>a.WeakConnect(O.Action, o1)
>a.Connect(o2.Action)
>
>print "Part 2"
>a()
>
>print "Part 3"
>o1 = None
>a()
>
>print "Part 4"
>o2 = None
>a()
>
>a.DisconnectAll()
>
>def f1():
> print "Hello Neighbor"
>
>def f2():
> print "Back to Work"
>
>c1 = a.Connect(f1)
>c2 = a.Connect(f2)
>
>print "Part 5"
>a()
>
>print "Part 6"
>a.Disconnect(c2)
>a()
>
>a.DisconnectAll()
>
>def f1(name):
> print "Hello %s" % name
>
>def f2(name):
> print "Goodbye %s" % name
>
>a.Connect(f1)
>a.Connect(f2)
>
>print "Part 7"
>#a() # Error
>a("Sarah")
>
>a.DisconnectAll()
>
>class MySignal(Signal):
> class Accumulator:
> def __init__(self):
> self.value = 0
> def __call__(self, value):
> self.value += value
> return bool(value != 0)
> def Finalize(self):
> return self.value
>
>
>def f1(x):
> return x * x
>
>def f2(x):
> return x + x
>
>def f3(x):
> return 0
>
>a = MySignal()
>a.Connect(f1)
>a.Connect(f2)
>a.Connect(f3)
>
>print "Part 8"
>print a(5)
>
>
>-----
>--
>http://mail.python.org/mailman/listinfo/python-list
More information about the Python-list
mailing list