Connect Python objects to blinker signals
Tags: python
Published: 2019-10-23 18:02:00

I started using [blinker](https://pythonhosted.org/blinker/) for [handroll](http://handroll.github.io/).  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](http://dictionary.reference.com/browse/frobnicate)" signal.  Remember, the signal itself is not very important.

```python
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](https://github.com/getpelican/pelican/blob/master/pelican/signals.py) does this nicely.  Grouping all your signals in one place gives signal consumers a clear view of what is available.

```python
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](http://en.wikipedia.org/wiki/Closure_%28computer_programming%29) on `self` which lets the signal call the instance method.

```python
        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.

```python
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:

```console
$ 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](https://docs.djangoproject.com/en/1.7/topics/class-based-views/intro/) in Django rather than function views.

Here's the full example.

```python
import blinker

frobnicated = blinker.signal('frobnicated')


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'])


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

This article first appeared on [mattlayman.com](https://www.mattlayman.com/blog/2015/blinker/).