What is a decorator
A decorator is essentially a Python function that allows other functions to add extra functionality without making any code changes, and the return value of the decorator is also a function object. It is often used in scenarios with faceted requirements, such as logging insertion, performance testing, transaction processing, caching, and permission verification. Decorators are a great way to solve this problem, because with decorators we can strip away a lot of code that has nothing to do with the function itself and continue to reuse it.
In a nutshell, the purpose of a decorator is to add extra functionality to an existing function or object.
How do YOU define a decorator
Python’s decorator is a fixed form of function interface. It requires that your decorator function (or decorator class) must accept a function and return a function:
Def wrapper(func1): # Accept a callable object return func2 # Return an object, usually @wrapper # call def target_func(args): pass using the @ syntaxCopy the code
If the decorated function needs to pass in arguments, you can specify the wrapper function to accept the same arguments as the original function
Def debug(func): def wrapper(something): print ("[debug]: enter{} ()".format(func.__name__)) return func(something) return wrapper @debug def say_hello(something): print("hello {}!" .format(something)) say_hello("ly")Copy the code
However, there are thousands of functions, and there is no guarantee that all functions will have the same parameters. So you can use Python’s mutable arguments *args and keyword arguments **kwargs. With these arguments, the decorator can be used for any target function.
def debug(func): def wrapper(*args, **kwargs): print ("[DEBUG]: enter{} ()".format(func.__name__)) return func(*args, **kwargs) return wrapper @debug def say_hello(something): print("hello {}!" .format(something)) say_hello("ly")Copy the code
Decorator with parameters
Suppose our previous decorator needs to do more than just print the log message when entering a function, but also specify the log level, then the decorator looks like this.
def logging(level): def wrapper(func): def inner_wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format(level=level, func=func.__name__)) return func(*args, **kwargs) return inner_wrapper return wrapper @logging(level='INFO') # =log (level='INFO')(say) def say(something): print("say {}!" .format(something)) @logging(level='DEBUG') def do(something): print("do {}..." .format(something)) if __name__ == '__main__': say('hello') do("my work") # [INFO]: enter function say() # say hello! # [DEBUG]: enter function do() # do my work...Copy the code
Student: Is the original function still the original function?
def my_decorator(func): def wrapper(*args, **kwargs): print('wrapper of decorator') func(*args, **kwargs) return wrapper @my_decorator def greet(message): Print (greet) print(greet.__name__Copy the code
As you can see, the greet() function is decorated with a different meta-information. Meta-information tells us “It’s not the greet() function anymore, it’s the Wrapper () function.”
To solve this problem, we usually use the built-in decorator @functools.wrap, which helps preserve the meta information of the original function (that is, copy the meta information of the original function into the corresponding decorator function).
import functools def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('wrapper of decorator') func(*args, **kwargs) return wrapper @my_decorator def greet(message): Print (message) greet.__name__ # print 'greet'Copy the code
Class decorator
Classes can also be used as decorators. The class decorator relies primarily on the function __call__(), which is executed every time you call an instance of a class.
class Count: def __init__(self, func): self.func = func self.num_calls = 0 def __call__(self, *args, **kwargs): self.num_calls += 1 print('num of calls is:{}'.format(self.num_calls)) return self.func(*args, **kwargs) @Count def example(): Print ("hello world") example() example() example() output: num of calls is:1 hello world num of calls is:2 hello world num of calls is:3 hello worldCopy the code
Here, we define the class Count, pass the original function func() to initialize, and the __call__() function means increment num_calls by one, print, and call the original function. So the first time we call example(), num_calls has the value 1, and the second time it has the value 2.
Nesting of decorators
Python also supports multiple decorators, for example
@decorator1
@decorator2
@decorator2
def func():
...
Copy the code
It is executed from the inside out, so the above statement is also equivalent to the following line:
decorator1(decorator2(decorator3(func)))
Copy the code
Examples of decorator usage
The identity authentication
For example, on some websites, you can browse content without logging in, but if you want to post an article or leave a comment, when you click publish, you will check whether you are logged in or not
import functools
def authenticate(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
request = args[0]
if check_user_logged_in(request):
return func(*args, **kwargs)
else:
raise Exception('Authentication failed')
return wrapper
@authenticate
def post_comment(request, ...):
...
Copy the code
logging
In practice, decorators are a useful tool to test at least one function online if you suspect it will take too long, resulting in an increase in latency across the system.
import time
import functools
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
res = func(*args, **kwargs)
end = time.perf_counter()
print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
return res
return wrapper
@log_execution_time
def calculate_similarity(items):
...
Copy the code
Here, the decorator log_execution_time records the running time of a function and returns the result of its execution. If you want to calculate the execution time of any function, you just add @log_execution_time above it.
Input rationality check
The use of decorators to check the validity of the input can greatly avoid the heavy machine overhead caused by incorrect input.
import functools def validation_check(input): @functools.wraps(func) def wrapper(*args, **kwargs): ... @validation_check def neural_network_training(param1, param2...) :...Copy the code
The cache
Python’s built-in LRU cache is @lru_cache. @lru_cache Caches function parameters and results. When the cache is full, the least recently used data is deleted. Correct use of cache decorator, often can greatly improve the efficiency of the program.
For example: Large companies tend to have a lot of device checks in their server-side code, such as whether you’re using an Android or aN iPhone, and what version number. One reason for this is that there are new features that are only available on certain mobile systems or versions (such as Android V200 +). As a result, we usually use a cache decorator to wrap these checking functions so that they are not called repeatedly, thus improving the efficiency of the program, such as the following:
@lru_cache def check(param1, param2, ...) Check user device type, version number, etc...Copy the code