First look at this @wrap grammar , confused , Let's analyze it carefully
Let's take a look at the following example , There is a summation function add, Now ask to count the length of time the function is executed
def add(a, b):
print(a+b)
It's easier to think of
import time
def add(a, b):
start = time.time() # Starting time
print(a + b)
time.sleep(4) # Simulation time operation
long = time.time() - start # Calculate the time interval
print(f' Total time consuming {long} second ')
By doing so, you can achieve the requirements , But the original function has been modified , It not only increases the coupling , Extension and reuse also become difficult to achieve .
If you add another function of logging , And time length statistics of all functions in the program , Think about it .
therefore , We put add As a parameter , You can write like this :
import time
def timer(func,*args):
start = time.time()
func(*args)
time.sleep(4) # Simulation time operation
long = time.time() - start
print(f' Total time consuming {long} second ')
timer(add,1,2)
That doesn't change the original function, does it ? Yes , But it changed the way function calls , Every call add All of them need to be modified , Doing so just passes on the contradiction .
also The original function cannot be modified , also You can't change the calling method , So what should we do ? It's time for the decorator to appear .
Without changing the original function code , Add extra features . Take an example
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs) # Get the decorated function here ——func, And execute the function
time.sleep(2)# Simulation time operation
long = time.time() - start
print(f' Total time consuming {long} second .')
return wrapper # Returns a reference to an inner function
@timer
def add(a, b):
print(a+b)
add(1, 2) # Normal call add
timer We transformed it into a decorator , It accepts decorated functions as input parameters , Returns a reference to an internally nested function ( Be careful : The function... Is not executed here ), Internal nested functions wrapper Hold a reference to the decorated function, that is func.
Look at the code and you'll understand it well , Decorators are similar to C++ Function pointer of ,C# Commission of . and “@” yes Python The grammar sugar of , Its function is similar to :
add = timer(add) # What's returned here is timer.<locals>.wrapper Function reference for
add(1, 2)
To sum up add Function execution flow :
Module loading --> encounter @, perform timer function , Pass in add function --> Generate timer.<locals>.wrapper Function and name it add--> call add(1, 2)--> To carry out timer.<locals>.wrapper(1, 2) --> wrapper Internally held original add Function reference (func), call func(1, 2) --> Go ahead and finish wrapper function
ask : If there are multiple decorators , What is the order of execution ? Look at the code below
import time
def test1(func):
def wrapper(*args, **kwargs):
print('before test1 ...')
func(*args, **kwargs)
print('after test1 ...')
return wrapper # Returns a reference to an inner function
def test2(func):
def wrapper(*args, **kwargs):
print('before test2 ...')
func(*args, **kwargs)
print('after test2 ...')
return wrapper # Returns a reference to an inner function
@test2
@test1
def add(a, b):
print(a+b)
add(1, 2) # Normal call add
Think of the decorator as an onion , Wrap the function layer by layer from near to far , The execution is to take a knife and cut from one side , Until the end of cutting to the other side .
After understanding the decorator , We can think about it , How to write a decorator with parameters ? We know that the decorator ultimately returns a reference to a nested function , Just remember this , The decorator was left to us . Write a decorator with arguments :
3.1 Don't use @wraps Decorator
import time
def auth(permission):
def _auth(func):
def wrapper(*args, **kwargs):
print(f" Verify permissions [{permission}]...")
func(*args, **kwargs)
print(" completion of enforcement ...")
return wrapper
return _auth
@auth("add")
def add(a, b):
print(a + b)
add(2,3)
print(add.__name__)
print(add.__doc__)
notes : Don't use @wraps Decorator time , have a look __name__、__doc__ What is the output
We can see , The return is wrapper Method reference , Also is to let add Yes wrapper Method , So call add.__name__, It's actually wrapper.__name__, In this way, you may get the name and comment of the embedded function of the decorator when you look up the name and comment of the method later .
3.2 Use @wraps(func)
import time
from functools import wraps
def auth(permission):
def _auth(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f" Verify permissions [{permission}]...")
func(*args, **kwargs)
print(" completion of enforcement ...")
return wrapper
return _auth
@auth("add")
def add(a, b):
print(a + b)
print(add(2,3))
print(add.__name__)
print(add.__doc__)
summary :Python Decorator (decorator) At the time of realization , The decorated function is actually another function ( Function properties such as function name will change ), In order not to affect ,Python Of functools There is a package called wraps Of decorator To eliminate such side effects . Write a decorator When , It's better to add functools Of wrap, It can keep the name of the original function and docstring.
Reference resources :
python Decorator wraps effect _hqzxsc2006 The blog of -CSDN Blog _python3 wraps
Python Decorator depth analysis - You know