Usually, signal receivers are defined as functions and then connected to a specific signal via a function call to the signal instance's
connect method. The problem is, this registration of the receiver is located outside the receiver's definition. This can cause clutter, it violates DRY, and it is not very Pythonic in style. Several examples of the current usage pattern are included in the signal docs, so I don't need to go into too much detail.
I propose a change to the
connect method on the
django.dispatch.dispatcher.Signal class, which would allow you to decorate a signal receiver function and therefore skip the explicit call to the method. The usage of the new method would look something like this:
from django.db.models.signals import pre_save # Just an example of a signal.
@pre_save.connect(sender=MyModel) # Other keyword arguments may be given.
def receiver(sender, instance, *args, **kwargs):
pass # Do something here.
I've written a patch which does exactly this, preserving backwards compatibility also (although that's not the biggest issue right now; signals are a very recent addition). It works by moving the current
Signal.connect method to
Signal._connect, replacing it with a small wrapper around the original which, when called, does one of two things:
- If called with positional arguments (and, optionally, keyword arguments), it behaves like the old
Signal.connect, connecting the given receiver function to the signal. This allows it to be used as both a decorator and a registration function.
- If called with keyword arguments but no positional arguments, it returns a wrapper function, allowing it to decorate the receiver function and include the given keyword arguments (thanks to Python's lexical scoping).
This means it will work like so (with the effects easy to determine from the code):
from django.db.models.signals import pre_save
# Decorator with no args.
def recvr1(sender, instance, *args, **kwargs):
pass # ...
# Registration with only the receiver.
# Decorator with keyword args.
def recvr2(sender, instance, *args, **kwargs):
pass # ...
# Registration with both receiver and keyword args.