Two important concepts of decorator:
1'@'grammatical sugar
(2) Customization before and after execution without changing the original function code
The decorator is placed where a function begins to be defined, and it is worn like a hat on the head of the function. It's bound to this function. When we call this function, the first thing is not to execute it, but to pass this function as a parameter into the hat overhead, which we call the decorator, and the return value is the function object.
Use scenarios:
- Application scenarios:
- Flask: Routing, before_request, after_request
- Django: csrf, caching, built-in user login authentication
- functools: caching, warper
During an interview, the interviewer asked two questions:
1. What kind of functions have you used decorative devices to achieve?
2. How to write an ornament that can be passed on?
01. Hello, Decorator
The use of decorators is very fixed.
-
Define a decorator (hat)
-
Redefine your business functions or classes (people)
-
Finally, fasten the decorator (hat) on the head of the function (person).
It's like this
def decorator(func): # The func import calls the decorator function, retaining the original function of the function. def wrapper(*args, **kwargs): # wrapper indicates that the original function is encapsulated * args, ** kw can pass in multiple parameters if datetime.datetime.now().year == 2020: # New business logic print(datetime.datetime.now().year) return func(*args, **kwargs) #Call the original function else: print('Not pass oOooO') return wrapper @decorator def test(name): print('hello,{}!'.format(name)) test('haha')
In fact, decorators are not necessarily coding, that is to say, you can not use decorators, it should be the emergence of our code.
-
More elegant, more clear code structure
-
Encapsulating specific functional codes into decorators to improve code reuse and readability
02. Introduction: Log Printer
First is the log printer.
Functions implemented:
-
Before the function is executed, print a line of log to inform the host that I am going to execute the function.
-
After the function is executed, you can't pat your butt and walk away. We are polite code, and print a line of log to inform the host, I'm done.
#This is the decorator function. The parameter func is the decorated function. def logger(func): def wrapper(*args, **kw): print('Master, I'm ready to start executing:{} Function:'.format(func.__name__)) # What's really going on is this line. func(*args, **kw) print('Master, I'm done.') return wrapper
Suppose my business function is to calculate the sum of two numbers. When you have finished writing, put a hat on it directly.
@logger def add(x, y): print('{} + {} = {}'.format(x, y, x+y))
Then execute the add function.
add(200, 50)
Let's see what's output.
Master, I'm ready to start executing: add function: 200 + 50 = 250 Master, I'm done.
03. Introduction: Time Timer
Let's look at the timer again.
Implementing function: As the name implies, it is to calculate the execution time of a function.
#This is a decorative function. import time def timer(func): def wrapper(*args, **kw): t1 = time.time() func(*args,**kw) t2 = time.time() cost_time=t2 - t1 print("Spend time:{}second".format(cost_time)) return wrapper
If, our function is to sleep for 10 seconds. In this way, we can better see whether the calculation time is reliable or not.
import time @timer def want_sleep(sleep_time): time.sleep(sleep_time) want_sleep(10)
Let's look at the output, 10 seconds as expected.
Time spent: 10.0073800086975098 seconds
04. Advancement: Functional Decorator with Parameters
Through the above two simple introductory examples, you should be able to understand the working principle of the decorator.
However, the use of decorative appliances is far more than that. There are many articles to go into. Let's learn this knowledge thoroughly today.
Looking back at the example above, the decorator can't accept parameters. Its usage can only be applied to some simple scenarios. Decorators without parameters can only perform fixed logic on decorated functions.
Decorator itself is a function, as a function, if it can not pass parameters, then the function of this function will be very limited, can only execute fixed logic. This means that if the decorator's logic code execution needs to be adjusted according to different scenarios, if it can not be passed on, we have to write two decorators, which is obviously unreasonable.
For example, if we want to achieve a task that can send mail regularly (send a letter in a minute) and synchronize time regularly (synchronize once a day), we can implement a periodic_task decorator by ourselves. The decorator can receive parameters of a time interval and how long the interval is. Perform a task once in a while.
It can be written as follows. Because the function code is complex and not conducive to learning, it is not pasted here.
@periodic_task(spacing=60) def send_mail(): pass @periodic_task(spacing=86400) def ntp() pass
Then we create a false scene for ourselves. We can pass in a parameter in the decorator, specify nationality, and greet with our native language before the function is executed.
#Xiao Ming, Chinese @say_hello("china") def xiaoming(): pass #jack, American @say_hello("america") def jack(): pass
What if we implement this ornament so that it can be realized?
It will be more complex, requiring two layers of nesting.
def say_hello(contry): def wrapper(func): def deco(*args, **kwargs): if contry == "china": print("Hello!") elif contry == "america": print('hello.') else: return # Where functions are actually executed func(*args, **kwargs) return deco return wrapper
To execute
xiaoming() print("------------") jack()
Look at the output.
Hello! ------------ hello.
05. High Level: Class Decorator without Parameters
These are all function-based decorators. When you read other people's code, you can often find class-based decorators.
Based on the implementation of class decorator, two built-in functions _call_ and _init_ must be implemented.
_ init_: Receives decorated functions
_ call_: Implement decorative logic.
Or take the simple example of log printing
class logger(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("[INFO]: the function {func}() is running..." .format(func=self.func.__name__)) return self.func(*args, **kwargs) @logger def say(something): print("say {}!".format(something)) say("hello")
Execute and see the output
[INFO]: the function say() is running... say hello!
06. High order: parametric class ornaments
The above example without parameters, you found that no, can only print INFO level logs, under normal circumstances, we also need to print DEBUG WARNING level logs. This requires passing in parameters to the class decorator and assigning a level to the function.
Class decorators with and without parameters are quite different.
_ init_: No longer receives the decorated function, but receives the incoming parameters.
_ call_: Receives the decorated function to implement the decorated logic.
class logger(object): def __init__(self, level='INFO'): self.level = level def __call__(self, func): # Acceptance function def wrapper(*args, **kwargs): print("[{level}]: the function {func}() is running..." .format(level=self.level, func=func.__name__)) func(*args, **kwargs) return wrapper #Return function @logger(level='WARNING') def say(something): print("say {}!".format(something)) say("hello")
Let's specify the WARNING level and run it to see the output.
[WARNING]: the function say() is running... say hello!
07. Implementing Decorators with Partial Functions and Classes
Most decorators are based on functions and closures, but this is not the only way to make decorators.
In fact, Python has only one requirement for whether an object can be used as a decorator (@decorator): the decorator must be a "callable" object.
For this callable object, we are most familiar with functions.
In addition to functions, classes can also be callable objects, as long as the _call_ function is implemented (the above examples have been touched on).
Another easily ignored partial function is actually a callable object.
Next, let's talk about how to use classes and partial functions to achieve a unique decorator.
As shown below, DelayFunc is a class that implements _call_. Delay returns a partial function, where delay can be used as a decorator. (The following code is taken from Python craftsmen: tips for using decorators)
import time import functools class DelayFunc: def __init__(self, duration, func): self.duration = duration self.func = func def __call__(self, *args, **kwargs): print(f'Wait for {self.duration} seconds...') time.sleep(self.duration) return self.func(*args, **kwargs) def eager_call(self, *args, **kwargs): print('Call without delay') return self.func(*args, **kwargs) def delay(duration): """ //Decorator: Delays the execution of a function. //Meanwhile, the. eager_call method is provided for immediate execution. """ # To avoid defining additional functions, # Direct use of functools.partial to help construct DelayFunc instances return functools.partial(DelayFunc, duration)
Our business function is simple, it's additive.
@delay(duration=2) def add(a, b): return a+b
Take a look at the execution process
>>> add # So add becomes an example of Delay <__main__.DelayFunc object at 0x107bd0be0> >>> >>> add(3,5) # Call the instance directly, and enter _call__ Wait for 2 seconds... 8 >>> >>> add.func # Implementing Example Method <function add at 0x107bef1e0>
08. How to write decorative articles?
There are three common ways to write singleton patterns in Python. One of them is to use decorative devices to achieve.
Here is my own example of the decorative version.
instances = {} def singleton(cls): def get_instance(*args, **kw): cls_name = cls.__name__ print('===== 1 ====') if not cls_name in instances: print('===== 2 ====') instance = cls(*args, **kw) instances[cls_name] = instance return instances[cls_name] return get_instance @singleton class User: _instance = None def __init__(self, name): print('===== 3 ====') self.name = name
You can see that we decorate the User class with the decorative function singleton. Decorators used in classes are not very common, but as long as you are familiar with the implementation process of the decorator, it is not difficult to achieve class decoration. In the above example, the decorator is just the control of the generation of class instances.
Its instantiation process, you can refer to my debugging process here, to understand.
09. What's the use of wraps?
There is a wraps decorator in the functools standard library. You should have seen it often. What's the use of it?
Let's start with an example.
def wrapper(func): def inner_function(): pass return inner_function @wrapper def wrapped(): pass print(wrapped.__name__) #inner_function
Why does this happen? Shouldn't we go back to func?
This is not difficult to understand, because the top execution func and the bottom decorator(func) are equivalent, so the top func. _name_ is equivalent to the bottom decorator(func). _name_ of course, the name is inner_function.
def wrapper(func): def inner_function(): pass return inner_function def wrapped(): pass print(wrapper(wrapped).__name__) #inner_function
How to avoid this situation? The method is to use functools. wraps decorator. Its function is to assign some attribute values of wrapped function to wrapper function, and finally make the display of attributes more in line with our intuition.
from functools import wraps def wrapper(func): @wraps(func) def inner_function(): pass return inner_function @wrapper def wrapped(): pass print(wrapped.__name__) # wrapped
To be precise, wraps is actually a partial function object with the following source code
def wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)
You can see that wraps is actually calling a function update_wrapper. Knowing the principle, we can rewrite the above code and let wrapped. _name_ print out wrapped without wraps. The code is as follows:
from functools import update_wrapper WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') def wrapper(func): def inner_function(): pass update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS) return inner_function @wrapper def wrapped(): pass print(wrapped.__name__)
10. Built-in Decorator: property
Above all, we introduce custom decorators.
In fact, Python language itself has some ornaments. For example, property is a built-in decorator that we are all familiar with.
It usually exists in classes, and a function can be defined as an attribute whose value is the content of the function return.
Usually we bind properties to instances like this
class Student(object): def __init__(self, name, age=None): self.name = name self.age = age # instantiation xiaoming = Student("Xiao Ming") # Add attribute xiaoming.age=25 # Query attribute xiaoming.age # Delete attribute del xiaoming.age
But a little experienced developers, it can be seen at a glance that this directly exposes the attributes, although it is simple to write, but the value of attributes can not be legitimate restrictions. In order to achieve this function, we can write as follows.
class Student(object): def __init__(self, name): self.name = name self.name = None def set_age(self, age): if not isinstance(age, int): raise ValueError('Input is not valid: age must be a value!') if not 0 < age < 100: raise ValueError('Illegal input: age range must be 0-100') self._age=age def get_age(self): return self._age def del_age(self): self._age = None xiaoming = Student("Xiao Ming") # Add attribute xiaoming.set_age(25) # Query attribute xiaoming.get_age() # Delete attribute xiaoming.del_age()
Although the above code design can define variables, it can be found that both acquisition and assignment (through functions) are different from what we usually see.
According to our thinking habits, this should be the case.
# assignment xiaoming.age = 25 # Obtain xiaoming.age
So how can we achieve this? See the code below.
class Student(object): def __init__(self, name): self.name = name self.name = None @property def age(self): return self._age @age.setter def age(self, value): if not isinstance(value, int): raise ValueError('Input is not valid: age must be a value!') if not 0 < value < 100: raise ValueError('Illegal input: age range must be 0-100') self._age=value @age.deleter def age(self): del self._age xiaoming = Student("Xiao Ming") # set a property xiaoming.age = 25 # Query attribute xiaoming.age # Delete attribute del xiaoming.age
A function decorated with @property defines a function as an attribute whose value is the content of the function return. At the same time, this function will be transformed into another decorator. Like the @age.setter and @age.deleter we used later.
@ age.setter allows us to assign values directly using XiaoMing.age = 25.
@ age.deleter allows us to delete attributes in a way like del XiaoMing.age.