Signals and Slots Implementation In Python

"Signals and slots are used for communication between objects. The signals and slots mechanism is a central feature of Qt and probably the part that differs most from the features provided by other frameworks". I literally copied that off from the QT Signals and Slots documentation. Until recently I didn't really use signal and slots much in my python code. I knew about them but did not really see the need. Until I started using QT and realized how great they are at decoupling my classes.

Below is an implemntation that I use a lot.

from __future__ import print_function
import inspect
from weakref import WeakSet, WeakKeyDictionary

class Signal(object): 
    def __init__(self):
        self._functions = WeakSet()
        self._methods = WeakKeyDictionary()

    def __call__(self, *args, **kargs):
        # Call handler functions
        for func in self._functions:
            func(*args, **kargs)

        # Call handler methods
        for obj, funcs in self._methods.items():
            for func in funcs:
                func(obj, *args, **kargs)

    def connect(self, slot):
        if inspect.ismethod(slot):
            if slot.__self__ not in self._methods:
                self._methods[slot.__self__] = set()

            self._methods[slot.__self__].add(slot.__func__)

        else:
            self._functions.add(slot)

    def disconnect(self, slot):
        if inspect.ismethod(slot):
            if slot.__self__ in self._methods:
                self._methods[slot.__self__].remove(slot.__func__)
        else:
            if slot in self._functions:
                self._functions.remove(slot)

    def clear(self):
        self._functions.clear()
        self._methods.clear()


class Emitter(object):
    
    def __init__(self):
        self.changed= Signal()
        self.second_value_changed = Signal()
        
        self.value = None
        
    def set_value(self, value):
        self.changed(value)
    
    def set_second_value(self, value):
        self.second_valued_changed()
        
class Reciever(object):
    
    def emitter_updated(self, *args, **kwargs):
        
        print("Signal recieved")
        print(*args)
        print(**args)
              
              
e = Emitter()
r = Reciever()

e.changed.connect(r.emitter_updated)

Class Emitter

The "Emitter" class is an example of a class that will send a message whenever "value" is changed.

Class Receiver

The "Receiver" class has a method called "emitter_updated". This receives the signal whenever "value" is changed in the "Emitter" class.

Emitter and Receiver are decoupled.

The code snippet shows the Emitter and the Receiver class know nothing about each other. When we create an instance , we "connect" them as follows:

e = Emitter()
r = Reciever()

e.changed.connect(r.emitter_updated)