Decorated functions and methods
We first define two simple mathematical functions, one for calculating the sum of squares and one for calculating the difference of squares:
# get square sum def square_sum(a, b): return a**2 + b**2 # get square diff def square_diff(a, b): return a**2 - b**2 print(square_sum(3, 4)) print(square_diff(3, 4))
After having basic mathematical functions, we may want to add other functions to the function, such as printing input. We can rewrite the function to achieve this:
# modify: print input # get square sum def square_sum(a, b): print("intput:", a, b) return a**2 + b**2 # get square diff def square_diff(a, b): print("input", a, b) return a**2 - b**2 print(square_sum(3, 4)) print(square_diff(3, 4))
We have modified the definition of the function and added functions to the function.
Now, we use a decorator to implement the above modifications:
def decorator(F): def new_F(a, b): print("input", a, b) return F(a, b) return new_F # get square sum @decorator def square_sum(a, b): return a**2 + b**2 # get square diff @decorator def square_diff(a, b): return a**2 - b**2 print(square_sum(3, 4)) print(square_diff(3, 4))
Decorators can be defined in the form of def, such as decorator in the above code. The decorator receives a callable object as an input parameter and returns a new callable object. The decorator creates a new callable object, which is new_F above. In new_F, we added the printing function and implemented the functions of the original function by calling F(a, b).
After defining the decorator, we can use it through the @ syntax. By calling @decorator before the functions square_sum and square_diff are defined, we actually pass square_sum or square_diff to the decorator, and assign the new callable object returned by the decorator to the original function name (square_sum or square_diff). So, when we call square_sum(3, 4), it is equivalent to:
square_sum = decorator(square_sum) square_sum(3, 4)
We know that variable names and objects in Python are separated. Variable names can point to any object. In essence, the decorator plays the role of re-pointing to the variable name (name binding), allowing the same variable name to point to a newly returned callable object, thereby achieving the purpose of modifying the callable object.
Similar to processing functions, we can use decorators to process class methods.
If we have other similar functions, we can continue to call decorator to decorate the function without repeatedly modifying the function or adding new packages. In this way, we improve the reusability of the program and increase the readability of the program.
Decorator with parameters
In the above decorator call, such as @decorator, the decorator defaults to the function following it as the only parameter. The syntax of decorators allows us to provide other parameters when calling decorator, such as @decorator(a). This provides greater flexibility in writing and using decorators.
# a new wrapper layer def pre_str(pre=''): # old decorator def decorator(F): def new_F(a, b): print(pre + "input", a, b) return F(a, b) return new_F return decorator # get square sum @pre_str('^_^') def square_sum(a, b): return a**2 + b**2 # get square diff @pre_str('T_T') def square_diff(a, b): return a**2 - b**2 print(square_sum(3, 4)) print(square_diff(3, 4))
The pre_str above is a decorator that allows parameters. It is actually a function encapsulation of the original decorator and returns a decorator. We can understand it as a closure containing environmental parameters. When we call using @pre_str('^_^'), Python can discover this layer of encapsulation and pass the parameters to the decorator environment. This call is equivalent to:
square_sum = pre_str('^_^') (square_sum)
Decoration
In the above example, the decorator receives a function and returns a function, thus achieving the effect of processing the function. After Python 2.6, decorators were extended to classes. A decorator can receive a class and return a class, thus having the effect of processing the class.
def decorator(aClass): class newClass: def __init__(self, age): self.total_display = 0 self.wrapped = aClass(age) def display(self): self.total_display += 1 print("total display", self.total_display) self.wrapped.display() return newClass @decorator class Bird: def __init__(self, age): self.age = age def display(self): print("My age is",self.age) eagleLord = Bird(5) for i in range(3): eagleLord.display()
In decorator, we return a new class newClass. In the new class, we record the object (self.wrapped) generated by the original class, and attach a new attribute total_display to record the number of times display is called. We also changed the display method at the same time.
After modification, our Bird class can display the number of times display is called.