Connect Python objects to blinker signals

By Matt Layman on March 18, 2015

I started using blinker for handroll. Blinker is a signal generation library for broadcasting events. The library lets signalers send messages to connected receiver functions. I will explain how I convinced Blinker to talk to objects instead of pure Python functions.

The example code is going to handle a “frobnicated” signal. Remember, the signal itself is not very important.

import blinker

frobnicated = blinker.signal('frobnicated')

frobnicated is a named signal. In a real project, you might put all your signals in a single module. Pelican does this nicely. Grouping all your signals in one place gives signal consumers a clear view of what is available.

class Receiver(object):

    def __init__(self):
        def handle_frobnicated(sender, **kwargs):
            self.on_frobnicated(sender, **kwargs)
        self.handle_frobnicated = handle_frobnicated
        frobnicated.connect(handle_frobnicated)

    def on_frobnicated(self, sender, **kwargs):
        print sender, kwargs['message']

The __init__ method is where all the magic happens. The first thing to notice is the use of an inner function, handle_frobnicated. The inner function uses the method signature that the signal will invoke, and delegates to Receiver.on_frobnicated. Why? This is necessary because Blinker can’t pass self to receiver functions. handle_frobnicated acts as a closure on self which lets the signal call the instance method.

        self.handle_frobnicated = handle_frobnicated

That seems like a strange line, doesn’t it? Blinker does some funny stuff with references. Without storing the inner function, Blinker will delete a weak function reference and the inner function will no longer be among the signal’s receivers. I stared at the Blinker source code for a long time to figure that mystery out.

The last line in __init__ connects the signal to the inner function. The receiver is ready to handle frobnicated events.

if __name__ == '__main__':
    receiver = Receiver()
    for i in range(10):
        frobnicated.send('Sender %s' % i, message='hello')

The code to fire the signal is fairly boring. Notice that frobnicated.send has no need for receiver. The publisher is disconnected from subscriber at this stage. The final result looks like:

$ python blink_object.py
Sender 0 hello
Sender 1 hello
Sender 2 hello
Sender 3 hello
Sender 4 hello
Sender 5 hello
Sender 6 hello
Sender 7 hello
Sender 8 hello
Sender 9 hello

By connecting a signal to an object, you get all the benefits that come along with classes. Rather than making a monsterous function, you could use various instance methods within the handler. This flexibility is a boon for unit testing. The gain has similar advantages to using class based views in Django rather than function views.

You can check out the full example in all its glory.

If you want to chat about this with me, I'm @mblayman on Twitter.



Matt Layman

Matt is the lead software engineer at Storybird.

Always eager to talk about Python and other technology topics, Matt organizes Python Frederick in Frederick, Maryland (NW of Washington D.C.) and seeks to grow software skills for people in his community.