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

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.

Python, 161 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
 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!")

4 comments

Ian Bicking 16 years, 3 months ago  # | flag

Advantages...? What's the advantage of this over PyDispatcher?

Frederic Mantegazza 16 years, 3 months ago  # | flag

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:

class BorgListener(object):
    __state = {}
    __init = True

    def __new__(cls, *args, **kwds):
        self = object.__new__(cls, *args, **kwds)
        self.__dict__ = cls.__state
        return self

    def __init__(self):
        if BorgListener.__init:
            self.val = 1
            BorgListener.__init = False

    # a sample method that will be connected to the signal
    def onClick():
        print "onClick: val = %d" % BorgListener().val
    onClick = staticmethod(onClick)

b = Button()
b.sigClick.connect(BorgListener().onClick)
BorgListener().val = 2
b.sigClick()
b.sigClick.disconnect(BorgListener().onClick)
b.sigClick()

Result:

onClick: val = 2

Patrick Chasco (author) 16 years, 3 months ago  # | flag

What's a borg method?

Frederic Mantegazza 16 years, 3 months ago  # | flag

Borg method. See this recipe about Borg/Singleton:

http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531
Created by Patrick Chasco on Wed, 17 Aug 2005 (PSF)
Python recipes (4591)
Patrick Chasco's recipes (1)

Required Modules

Other Information and Tasks