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