Django describes signal as a “signal dispatcher” that triggers multiple applications, mainly in the form of signals. This article will explain how signals work in Django and how to use them from the perspective of source code analysis.
Signal class
The most common scenario for signal is notifications. For example, if your blog has comments, the system will have a notification mechanism to push the comments to you. If you use signal, you only need to trigger the signal notification when the comment is posted, instead of putting the notification logic after the comment is posted, which greatly reduces the program coupling and facilitates the maintenance of the system later.
Django implements a Signal class, which is used to implement the function of “signal dispatcher”. The working mechanism is shown in the figure below, which is divided into two parts: first, each callback function that needs to be dispatched is registered to signal, and second, the event triggers sender
to send the signal.
receiver
The signal maintains a list receiver
of callback functions and their id
s that connect to the signal. Each receiver
must be a callback function and accepts the keyword argument **kwarg
, and the signal’s connect
method is used to connect the callback function to the signal.
Let’s take a look at the source code of connect
, as follows.
|
|
The code is very clean and is divided into four parts: checking parameters, getting the ID of receiver
, receiver
weak references, and locking. Here we mainly look at the last two parts.
receiver weak reference
Preparatory Knowledge.
Weak references: Python’s approach to garbage collection is to mark references, and the role of weak references is to avoid memory leaks caused by circular references. The main principle is that when a weak reference is made to an object, the number of references is not added to the reference mark, so when the strong reference to the object is 0, the system still reclaims it, and the weak reference fails.
method and function: Python’s functions are the same as those of other languages, including function names and function bodies, and supporting formal parameters; compared to functions, methods have an additional layer of class relationships, that is, they are functions defined in classes. This function is used to configure method
step by step, see an excerpt of the source code.
|
|
In addition to the function attribute im_func
, there is an im_self
attribute table self
and an im_class
attribute table class
in method
.
Bound Method and Unbound Method: Methods can be divided into bound
methods and unbound
methods. The difference is that bound
methods have an additional layer of instance binding, i.e., bound method
invokes a method through an instance, while unbound method
invokes a method directly through a class.
Weak references in signal:
Anyone familiar with Python garbage collection should know that Python only garbage collects an object when its reference count is 0. Therefore, all references to callback functions in signal are weakly referenced by default to avoid memory leaks.
First, the weak
argument to connect
indicates whether to use a weak reference, and the default is True
; receiver
can be either a function or a method, and the reference to bound method
is short-lived, in line with the lifetime of the instance, so the standard weak reference is not enough to keep it, and weakref. WeakMethod
to emulate the weak reference of bound method
; finally the weakref.finalize
method returns a callable terminator object that is called when receiver
is garbage collected, unlike normal weak references, the terminator will always be alive before it is called and die after it is called, thus greatly simplifying life cycle management.
Locking
Locks exist to achieve thread safety, which means that when multiple threads are present at the same time, the result still meets expectations. Obviously, the receiver
registration process in signal is not inherently thread-safe; the thread-safe method in signal is locking to achieve atomic operation of the connect
method.
Locks are defined in the signal’s __init__
method, using Lock
from the standard library.
|
|
signal encapsulates the three operations of cleaning up weakly referenced objects in the receiver
list, adding elements to the receiver
list, and cleaning up the global cache dictionary into atomic operations using thread locks, as follows.
sender
To be precise, the sender
in signal is an identifier to record “who” triggered the signal, what really works is the send
method, which is used in the event
to trigger the signal to all receivers
“in event
. Here is the source code for send
.
It is easy to see that the process of triggering all the recorded callback functions is synchronous, so signal is not suitable for handling large batches of tasks, but we can rewrite it as asynchronous tasks.
How to use signal
The use of signal requires only two configurations, one is the registration of the callback function, and the other is the event triggering.
There are two ways to register callback functions, one is the regular signal.connect()
; the other is that Django signal provides a decorator receiver
, which can be decorated by passing in which signal it is, or you can specify sender
, and if you don’t specify it, you receive all the information sent by sender
. Event triggering is done by calling <your_signal>.send()
where it can be triggered. A demo is given below.
|
|
Output.
Django’s built-in signals
Django has a number of built-in signals that we can use directly.
Model-related
|
|