SoFunction
Updated on 2024-11-17

Explaining Python Decorators from the Ground Up

Decorator function in many languages, the name is not the same, in fact, it embodies a design pattern, emphasizes the principle of open and closed, more for later functionality upgrades rather than writing new code. Decorators can decorate not only functions, but also other objects, such as classes, but usually, we use decorating functions as an example to introduce its usage. To understand the principle of decorators in Python, you need to take a step by step approach. In this article, we will try to describe it as easy to understand as possible, starting from the very basics.

(Note: the following uses the Python 3.5.1 environment)

I. Python's function-related fundamentals

First, it must be emphasized that python is executed sequentially from top to bottom, and the definition block of a function will not be executed immediately when it is encountered, but only when the function is called, the internal code block will be executed.

def foo():
print("The foo function was run!") 
If that's all there is to it.,fooThe statements in this section are not executed.。
The program simply reads the definition code block into memory。

Look, again, at the example of sequential execution:

def foo():
 print("I am the function definition above!")
def foo():
 print("I am the definition of the function below!")
foo()
running result:
I am the following function definition

As you can see, the foo below overwrites the foo above because of the sequential execution. Therefore, the placement of code in Python is required, not randomly, the function body should be placed before the called statement.

 Secondly, we have to figure out a couple of things first:Function name, function body, return value, memory address of the function, function name in parentheses, function name taken as an argument, function name in parentheses taken as an argument, return function name, return function name in parentheses.For the following function:

 def foo():
 print("Let's do something!")
 return "ok"
 foo()  

  Function name: foo

Function body: lines 1-3

Return value: the string "ok". If the object of return is not explicitly given, then None is returned by default.

Memory address of the function: the location where the body of the function is saved when it is read into memory, which is referenced by the identifier, i.e., the function name foo.
That is, foo points to the location in memory where the body of the function is stored.

Function name in parentheses: e.g. foo(), the method of calling the function, only if you see the parentheses, the program will be based on the
The function name finds the function body from memory and executes it

Look at the following example again:

def outer(func):
 def inner():
 print("I'm an inner function!")
 return inner
def foo():
 print("I am the primitive function!") 
outer(foo)
outer(foo())

Everything is an object in python, and functions are no exception. So you can call a function by its name, or even by its name in parentheses, as the return value of another function. In the above code, outer and foo are two functions. outer(foo) means that the name of the foo function is passed as a parameter to the outer function and executes the outer function; outer(foo()) means that the return value after the execution of the foo function is passed as a parameter to the outer function and executes the outer function, since the foo function does not specify a return value, it actually passes the return value to the outer function. Since the foo function does not specify a return value, it actually passes None to the outer function. note the difference, the presence or absence of parentheses is critical!

 Similarly, inside the inner function, the inner function returns an inner, which is a function defined inside the inner function. Note that since inner is not followed by parentheses, what is returned is the body of the inner function, which is actually the name inner, a simple reference. So what if the outer function returns inner()? Now you should have been very clear, it will first execute the contents of the inner function, and then return a None to outer, outer then return the None to the object that called it.

 Remember that function names, functions with parentheses can be passed as arguments or as return values RETURN, with or without parentheses are two very different meanings!

II. Scenarios for the use of decorators

Decorators are often used to add extra functionality to a function without changing the code and functionality of the original function. For example, to perform something before the original function is executed, and to perform something after it is executed.

 Let's take a look at an example of a scenario where a decorator is used and the design pattern it embodies. (I'm sorry I can't come up with a better scenario, so I'll have to quote the case of Wu Da Shen to illustrate it)

 There is a large company, the subordinate infrastructure platform department is responsible for the development of internal applications and APIs, there are hundreds of business departments are responsible for different businesses, they each call the infrastructure platform department to provide different functions to deal with their own business, the situation is as follows:

# Hundreds of functions developed by the Infrastructure Platform division
def f1():
 print("Business Unit 1 Data Interface ......")
def f2():
 print("Business Unit 2 Data Interface ......")
def f3():
 print("Business Unit 3 Data Interface ......")
def f100():
 print("Business Sector 100 Data Interface ......") 
# Separate calls for each department
f1()
f2()
f3()
f100()

Since the company was in its early days of business, when the basic platform department developed these functions, they did not authenticate security for the function calls due to various reasons, such as time, such as ill-considered and so on. Now, the head of the platform department decided to make up for this shortcoming, so:

  The first time, the supervisor called in an Ops engineer, who ran up and down department by department to notify them to add the authentication feature to the code, however, he was fired that day.

 The second time: the supervisor called another operation and maintenance engineer, the engineer wrote a complex script with the shell, barely realized the function. But he quickly went back to work as an O&M engineer. An O&M engineer who can't develop is not a good O&M engineer. ....

 Third time around: the supervisor called in a python automation development engineer, and here's what dude did:Refactor only the underlying platform code so that N business units don't need to make any changes. The dude was soon fired as well and didn't even get to do Ops.

def f1():
 #Add authentication program code
 print("Business Unit 1 Data Interface ......")
def f2():
 # Add certification program code
 print("Business Unit 2 Data Interface ......")
def f3():
 # Add certification program code
 print("Business Unit 3 Data Interface ......")
def f100():
 #Add authentication program code
 print("Business Sector 100 Data Interface ......")
# Separate calls for each department
f1()
f2()
f3()
f100()

 Fourth time: the supervisor has changed the engineer again, and here's what he does: he defines an authentication function, which is called by the original other function, in the following box. However, the supervisor is still not satisfied, but this time he explains why. The supervisor says: write code that follows thethe principle of openness and closureAlthough in this principle is mainly for object-oriented development, but also applies to functional programming, in short, it provides that the already realized functional code is not allowed to be modified internally, but can be extended externally, that is: closed: the realized functional code block; open: open to extension. If the open-closed principle is applied to the above requirements, then it is not allowed to make changes in the internal code of the functions f1 , f2 , f3..... .f100 code changes inside. Unfortunately, the engineer did not have a pretty girlfriend, so he was soon fired as well.

def login():
 print("Authentication successful!")
def f1():
 login()
 print("Business Unit 1 Data Interface ......")
def f2():
 login()
 print("Business Unit 2 Data Interface ......")
def f3():
 login()
 print("Business Unit 3 Data Interface ......")
def f100():
 login()
 print("Business Sector 100 Data Interface ......")
# Separate calls for each department
f1()
f2()
f3()
f100()

Fifth time around: there's no more time for the supervisor to find someone else to do the job, he decides to do it himself and plans to add a logging feature after the function executes. Here's what the supervisor thinks: a supervisor who doesn't know how to use decorators is not a good coder! Why else would I be in charge and you be in charge? Heh heh heh. His code is as follows:

#/usr/bin/env python
#coding:utf-8
def outer(func):
 def inner():
 print("Authentication successful!")
 result = func()
 print("Log added successfully")
 return result
 return inner
@outer
def f1():
 print("Business Unit 1 Data Interface ......")
@outer
def f2():
 print("Business Unit 2 Data Interface ......")
@outer
def f3():
 print("Business Unit 3 Data Interface ......")
@outer
def f100():
 print("Business Sector 100 Data Interface ......")
# Separate calls for each department
f1()
f2()
f3()
f100()

For the above code, it is only necessary to expand the code of the basic platform, it can be realized in other departments to call the function f1 f2 f3 f100 before the authentication operation, at the end of the operation to save the log, and other business sectors do not need to make any changes to their own code, the way to call does not have to change. "Supervisor" after writing the code, think alone is not as happy as all happy, intend to show off a little, so wrote a blog will be the process of a detailed description.

III. Internal principles of decorators,

 Let's take the f1 function as an example for illustration:

 def outer(func):
 def inner():
 print("Authentication successful!")
 result = func()
 print("Log added successfully")
 return result
 return inner
@outer
def f1():
 print("Business Unit 1 Data Interface ......")

Apply the knowledge we presented in Part I to analyze the code above:

  • The program began to run, compiled from top to bottom, read def outer(func)::, found that this is a "first-class citizen" - & gt; function, so the function body loaded into memory, and then over.
  • When reading @outer, the program is attracted to the syntactic sugar @, and knowing that this is a decorator, which by rule is to be executed immediately, the program begins to run the function defined by the name outer after @. (I'm sure no one would be stupid enough to write @outer anywhere else; it should only be placed closest above the function being decorated, without a blank line.)
  • The program returns to the outer function and starts executing the rules of the decorator's syntax. This part of the rules is fixed, it's the "law" of python, don't ask why. The rules are:The name of the decorated function is passed as an argument to the decorated function. After the decorated function executes its own internal code, it assigns its return value to the decorated function. 

As shown in the figure below:

Here's something to keep in mind:

  • There is a difference between @outer and @outer() in that without parentheses, the outer function is still executed, which is different from the traditional use of parentheses in order to call a function, and requires special attention! What about with parentheses? That's an advanced use of decorators, which will be covered later.
  • It is the function name f1 (not f1() when called as such) that is passed as an argument to the decorator function outer, i.e.: func = f1, @outer is equal to outer(f1),which in fact passes the body of the function f1, not the return value after the execution of f1.
  • The outer function RETURNS the name of the function INNER, not the return value when it is called like INNER().

If you have a clear understanding of the basics of the first part of the function, then the above should be easy for you to understand.

 4. The program starts executing the contents of the inner function of the inner function, and at first it encounters another function, which is quite a detour, isn't it? Of course, you could put some other code before and after the inner function, but they are not the point, and they are a little tricky, as explained below. inner function definition blocks are not executed immediately after they are observed by the program, but are read into memory (this is a subterfuge).

 5. Further down the line, we come to return inner, which is a function name that is assigned to the decorated function f1, that is, f1 = inner. Based on the previous knowledge, we know that the f1 function is now overwritten by a new function, inner (in effect, f1 is changed to point to the memory address of the body of the function that the inner function points to), and subsequent calls to f1 will execute the code inside the inner function instead of the body of the previous function. memory address, f1 no longer points to the memory address of its original function body), and then call f1 will execute the code in the inner function, not the previous function body. So where did the previous function body go? Remember that we passed f1 as a parameter to func, the variable that holds the address of the old function in memory, so that you can execute the old body of the function, as you can see in the inner function, result = func(), and that's what it does!

 6. Next, it's not over. When the business sector, still through the f1 () way to call the f1 function, the implementation of the old f1 function is no longer the code, but the inner function of the code. In this case, it will first print a "successful authentication" prompt, obviously you can change to any code, this is just an example; then, it will execute the func function and the return value will be assigned to a variable result, the func function is the old f1 function; then, it printed the It then prints the "log saved" prompt, which is also just an example and can be changed to whatever you want, and finally returns the result variable. We can use r = f1() to accept the value of result in our business code.

 7. After the above process, you should see, in the absence of business sector code and interface calls to do any changes at the same time, there is no basic platform for the Department of the original code to do internal modifications, just add a decorative function to achieve our needs, in the function call before the authentication, write the log after the call. This is the biggest role of the decorator.

 Question: So why are we making it so complicated to have an outer function and an inner function? Can't we have a layer of functions?

 A: Note that the @outer line of code automatically executes the code inside the outer function as soon as the program executes to it, and without encapsulating it, it's a bit inconsistent with the original intent to execute something before the business unit has even made the call. Of course, if you have a need for this is not impossible. See the following example, which has only one layer of functions.

def outer(func):
 print("Authentication successful!")
 result = func()
 print("Log added successfully")
 return result
@outer
def f1():
 print("Business Unit 1 Data Interface ......")
# Operations did not start executing the f1 function
Implementation results:
Certification Success!
business unit1data interface......
Log added successfully

See? I just defined the function, and the business unit hasn't even called the f1 function yet, and the program did all the work. That's the reason for encapsulating a layer of functions.

IV. Parameter passing for decorators

Careful friends may have found that the above example, f1 function has no parameters, in the actual situation will certainly need parameters, that parameters how to pass?

 The case of one parameter:

def outer(func):
 def inner(username):
 print("Authentication successful!")
 result = func(username)
 print("Log added successfully")
 return result
 return inner
@outer
def f1(name):
print("%s Connecting to Business Unit 1 data interface ......"%name)
# Calling methods
f1("jack")

Add a parameter to the definition section of the inner function as well, and pass this parameter when calling the func function, it's very understandable, right? But then the question arises, what about the other department that calls f2 with 2 parameters, or f3 with 3 parameters? How do you pass it?

It's easy, we have *args and **kwargs! Known as "universal parameters"! Simply modify the code above:

def outer(func):
 def inner(*args,**kwargs):
 print("Authentication successful!")
 result = func(*args,**kwargs)
 print("Log added successfully")
 return result
 return inner
@outer
def f1(name,age):
 print("%s Connecting to Business Unit 1 data interface ......"%name)
# Calling methods
f1("jack",18)

V. Further reflections

 Can a function be decorated by more than one function? Yes, you can! Look at the example below!

def outer1(func):
 def inner(*args,**kwargs):
 print("Authentication successful!")
 result = func(*args,**kwargs)
 print("Log added successfully")
 return result
 return inner
def outer2(func):
 def inner(*args,**kwargs):
 print("A welcome message...")
 result = func(*args,**kwargs)
 print("A cheering message...")
 return result
 return inner
 @outer1
@outer2
def f1(name,age):
 print("%s Connecting to Business Unit 1 data interface ......"%name)
# Calling methods
f1("jack",18) 
Implementation results:
Certification Success!
A welcome message。。。
jack Business units being connected1data interface......
A message of farewell。。。
Log added successfully

Further, can decorators themselves have parameters? Yes, they can! Look at the following example:

# Authentication Functions
def auth(request,kargs):
 print("Authentication successful!")
# Log Functions
def log(request,kargs):
 print("Log added successfully")
# Decorator function. Receives two arguments, which should be the name of some function.
def Filter(auth_func,log_func):
 # The first level of encapsulation, the f1 function is actually passed the argument main_fuc
 def outer(main_func):
 # The second layer of encapsulation, where the parameter values for the auth and log functions are passed to the
 def wrapper(request,kargs):
 # The judgment logic of the following code is not important, what matters is the parameter references and return values
 before_result = auth(request,kargs)
 if(before_result != None):
 return before_result;
 main_result = main_func(request,kargs)
 if(main_result != None):
 return main_result;
 after_result = log(request,kargs)
 if(after_result != None):
 return after_result;
 return wrapper
 return outer
# Note that the decorator function here has a parameter, which means that the filter function is executed first.
# Then the return value of the filter function is returned here as the name of the decorator function, so that the
# Actually here, Filter(auth,log) = outer , @Filter(auth,log) = @outer
@Filter(auth,log)
def f1(name,age):
 print("%s Connecting to Business Unit 1 data interface ......"%name)
# Calling methods
f1("jack",18)
running result:
Certification Success!
jack Business units being connected1data interface......
Log added successfully

Getting confused again? Actually you can understand it like this, first execute the Filter function, get its return value outer, and then execute the @outer decorator syntax.

The above is the whole content of this article, I hope that the content of this article can bring some help to your study or work, but also hope that more support me!