Decorators are widely used in applications such as logging and caching. Let's start with the closures explained before and explain the things about decorators.
First of all, let's review the closure, which can be regarded as the nesting of functions, and the function object is returned inside the function.
def nth(exponent): def exponent_of(base): return base ** exponent return exponent_of square = nth(2) cube = nth(3) print(square(5)) print(cube(5)) # 25 5^2 # 125 5^3
Here, what is passed in outside the closure is a parameter with a specific value. If it is passed in a function object? That completes a simple decorator.
def decorator(func): def wrapper(): print('start to decorate') func() print('start to decorate') return wrapper def test(): print('welcome to decorate') test1 = decorator(test) test1() #start to decorate #welcome to decorate #start to decorate
In this code, the test1 variable points to the function wrapper(), and the internal function wrapper() calls the test() function, so three paragraphs of text are printed at the end.
With @ Syntax Sugar, the above code can be written more concisely and more elegantly:
def decorator(func): def wrapper(): print('start to decorate') func() print('start to decorate') return wrapper @decorator def test(): print('welcome to decorate') test() #start to decorate #welcome to decorate #start to decorate
@Decorator here is equivalent to test1 = decorator(test) statement.
If parameters need to be added to the test function, we need to add the corresponding parameters to the wrapper function:
def decorator(func): def wrapper(message): print('start to decorate') func(message) print('start to decorate') return wrapper @decorator def test(message): print(message) test('hello world') #start to decorate #hello world #start to decorate
But a new problem is coming. If there is a new function that also needs the decorator() decorator, but it has two parameters, what should we do?
@decorator def test2(a,b): print(a+b)
In this case, we can use *args and **kwargs.
def decorator(func): def wrapper(*args,**kwargs): print('start to decorate') func(*args,**kwargs) print('start to decorate') return wrapper
The decorator can also customize its parameters, where one more level of nesting is needed to make the internal function run multiple times.
def repeat(num): def decorator(func): def wrapper(*args,**kwargs): print('start to decorate') for i in range(num): func(*args,**kwargs) print('start to decorate') return wrapper return decorator @repeat(3) def test(message): print(message) test('hello world') #start to decorate #hello world #hello world #hello world #start to decorate
After using the decorator, there is an interesting phenomenon, that is, the meta information of the decorated function is changed.
print(test.__name__) # wrapper
We can also use the built-in decorator @functools.wraps(func) to preserve the meta-information of the original function.
import functools def decorator(func): @functools.wraps(func) def wrapper(*args,**kwargs): print('start to decorate') func(*args,**kwargs) print('start to decorate') return wrapper @decorator def test(message): print(message) print(test.__name__) # test
Starting from the closure, this article explains the definition and use of the decorator. In fact, the role of the decorator is: through the decorator function, the original function can be modified without modifying the original function code.