The introduction of
Defining functions is similar to defining variables. Variable names bind the memory address of values, whereas function names bind the memory address of code blocks. Functions can also be used like variables.
The function object
The essence of function objects is that functions can be used as variables. Function objects can be used as follows:
- The function name is called in parentheses
A parenthesized call to a function name runs the function body code and can use variables to receive the function return value.
def func() :
print('func')
return 'func'
res = func()
print(res)
Copy the code
- You can assign
The memory address of the function body code after the function definition is complete, without parentheses.
def func() :
print('func')
print(func) The memory address of the function body code
foo = func # refer the name foo to the memory address of the func function body code
foo() # equivalent to func()
Copy the code
- Can be used as arguments to a function
def func(x) :
print(x)
x()
def foo() :
print('from foo')
func(foo)
# pass foo as an argument to func
Copy the code
- Can be used as the return value of the function
def func(x) :
x()
return x
def foo() :
print('from foo')
res = func(foo)
print(res)
Copy the code
- Can be used as an element of container type data
def func() :
print('from func')
def foo() :
print('from foo')
func_list = [func, foo]
for function in func_list:
function()
Copy the code
Nested function
Function nesting is understood in two ways:
Nested definitions of functions
Define other functions within a function.
def func() :
print('from func')
def foo() :
print('from foo')
Copy the code
Nested calls to a function
Call another function in the process of calling one function.
# Define a function: compare two values
def max2(x,y) :
if x > y:
return x
else:
return y# Define function: compare the size of four values
def max4(a,b,c,d) :
# Step 1, compare a and B
res = max(a,b)
# 2, compare RES and C
res1 = max(res,c)
Step 3, compare RES1 with D
res2 = max(res1,d)
return res2
res = max(2.3.5.1)
print(res)
Copy the code
The closure function
What is a closure function
A closed function is a function that is inside a function, that is, nested inside a function.
Package functions refer to references to scope words of inner functions and outer (non-global) functions.
Closure functions are based on function objects and can be called at any location, but the scope relationship is determined at the time the function is defined, regardless of where the function is called.
An inline function is a closure if it contains a reference to the name of an outer function’s scope.
def func() :
x = 100
def foo() :
print(x) # A reference to an external function scope word
foo()
return foo
res = func()
Copy the code
Closure functions
Closure functions provide an alternative way of passing parameters to functions, and decorators are also based on closure functions.
The parameters required by the function body are directly defined as parameters
def func(x, y) :
print(x, y)
# function pass parameter method 2: closure function, embedded function name reference outer layer function
def func(x) :
def foo() :
print(x)
Copy the code
A decorator
Why decorators
The design of software should follow the principle of open and closed, that is, open to extension and closed to modification of code. This means that you can extend existing code as new requirements or features come in, but don’t modify code once it’s designed to fulfill a requirement on its own.
It seems to be the opposite, because all the code contained in a program can change some of it, you have the butterfly effect, you have to change everything wrong, but you have to provide a way for the program to extend, and that’s where decorators come in.
What is a decorator
Decorator refers to adding something extra to embellish other transactions, and an agent refers to a tool, which can be defined as a function. Therefore, decorator refers to defining a function that is used to add extra functionality to other functions.
In the abstract, decorators add additional functionality to decorated objects without modifying their source code or how they are called. Decorators are often used for login verification, permission verification, etc. Decorators can be used to extract a lot of code irrelevant to the function itself and reuse it.
No argument decorator implementation
Decorators can be divided into parameterless decorators and parameterless decorators. The implementation principle is the same, based on closure functions, function nesting and function objects.
To derive the implementation of a no-argument decorator, use a simple example: calculate the execution time of a function.
Let’s start with the simple use of the time module, which can be used to calculate time.
import time # import module
start_time = time.time() # current time
end_time = time.time()
res = end_time - start_time # Subtract to get the time difference
Copy the code
Now to derive how to implement the no-argument decorator, calculate the execution time of the following functions.
import time
def func(x, y) :
time.sleep(3) Can sleep the application for 3s
print(x, y)
Copy the code
Solution 1
import time
def func(x, y) :
start_time = time.time()
time.sleep(3) Can sleep the application for 3s
print(x, y)
end_time = time.time()
print(end_time - start_time) # Function runtime
return x, y
func(1.2)
Copy the code
Although this scheme solves the need to calculate the execution time of functions, it violates the open and closed principle. Although the calling method is not changed, the source code of func functions is changed.
Solution 2
Add time before and after the function call, and subtract to get the running time
import time
def func(x, y) :
time.sleep(3) Can sleep the application for 3s
print(x, y)
return x, y
start_time = time.time() # current time
func(1.2)
end_time = time.time()
print(end_time - start_time)
Copy the code
Although this scheme solves the need to calculate the execution time of the function, it does not violate the principle of open and closed, but if the calculation of the running time of the function is a very common function, then the code will appear a lot of repeated parts, code redundancy.
Solution 3
The time calculation code in scheme 2 is encapsulated as a function to reduce code redundancy.
import time
def func(x, y) :
time.sleep(3) Can sleep the application for 3s
print(x, y)
return x, y
def wrapper() :
start_time = time.time() # current time
res = func(1.2)
end_time = time.time()
print(end_time - start_time)
wrapper()
Copy the code
This solution solves the need to calculate the running time of the function and reduces code redundancy, but it changes the way the original function is called, and the arguments of the func function are written out, so only the running time of func(1, 2) can be calculated.
Solution 4
The wrapper function can be defined with the same parameters as the func function, and the parameters of the func function can be written alive by passing parameters to the Wrapper function.
import time
def func(x, y) :
time.sleep(3) Can sleep the application for 3s
print(x, y)
return x, y
def wrapper(x, y) :
start_time = time.time() # current time
res = func(x, y)
end_time = time.time()
print(end_time - start_time)
wrapper(1.2)
Copy the code
This solution solves the need and fixes the problem of fixed arguments, but the invocation changes and the wrapper function parameters must be defined according to the format of func parameters. If the number of func parameters changes, the wrapper parameters must also change.
Solution 5
How to prevent the parameters of the Wrapper function from being affected by the parameters of the func function is the goal of this scheme. When defining the Wrapper function, we define the wrapper function as a variable length parameter that can be used to receive any syntactically correct argument as long as it is passed in the same way as func. In the definition phase, the Wrapper function can accept all arguments regardless of how func’s parameters change. There is no need to change the Wrapper parameter.
import time
def func(x, y) :
time.sleep(3) Can sleep the application for 3s
print(x, y)
return x, y
def wrapper(*args, **kwargs) :
start_time = time.time() # current time
res = func(*args, **kwargs) # here is the argument
end_time = time.time()
print(end_time - start_time)
wrapper(1.2)
Copy the code
This solution resolves the problem that the Wrapper function parameters are limited to func parameters, but the way the function is called has changed, and the Wrapper function can only count the runtime of the func function.
Solution 6
Func = func; func = func; func = func;
import time
def func(x, y) :
time.sleep(3) Can sleep the application for 3s
print(x, y)
return x, y
def wrapper(function, *args, **kwargs) :
start_time = time.time() # current time
res = function(*args, **kwargs) # here is the argument
end_time = time.time()
print(end_time - start_time)
wrapper(func, 1.2)
Copy the code
We can pass values to func, but we need to change the way the decorated object is called and the source code. We still change the way the original function is called, and the shape of the Wrapper function is inconsistent with the parameters of the func function. Therefore, consider using closure functions to pass arguments to embedded functions.
import time
def func(x, y) :
time.sleep(3) Can sleep the application for 3s
print(x, y)
return x, y
def outer(function) :
def wrapper(*args, **kwargs) :
start_time = time.time() # current time
res = function(*args, **kwargs) # here is the argument
end_time = time.time()
print(end_time - start_time)
return res Returns the return value of the decorated object
return wrapper
wrapper = outer(func)
wrapper(1.2)
Copy the code
Since the wrapper function itself is in the global namespace, it needs to pass arguments to the Wrapper function, so it is put in the local namespace. When passing arguments to the Wrapper function is finished, the Wrapper function needs to be put back into the global namespace.
This solution still does not solve the problem of how to call decorated objects.
Solution 7 – Final version
Outer can be assigned to any variable. It can be assigned to wrapper, and func can be assigned to wrapper. If you need a wrapper, return the wrapper from the decorated function.
import time
def func(x, y) :
time.sleep(3) Can sleep the application for 3s
print(x, y)
return x, y
def outer(function) :
def wrapper(*args, **kwargs) :
start_time = time.time() # current time
res = function(*args, **kwargs) # here is the argument
end_time = time.time()
print(end_time - start_time)
return res Returns the return value of the decorated object
return wrapper
func = outer(func)
func(1.2)
Copy the code
Now we’ve done this and we’ve added new functionality to func without changing the source code of func or the way we call it and now the memory address that func points to is not the memory address that func points to, but the memory address of the Wrapper function, and we’ve changed the name of the function to func.
No argument decorator summary
Through the derivation of the parameterless decorator, a universal template and the principle of parameterless decorator can be obtained.
def outer(function) : # outer is just a name, can you call it anything
def wrapper(*args, **kwargs) :
res = function()
return res
returnWrapper decorates the function name = outer# The principle of the no-parameter decoratorDecorated function name ()Copy the code
Syntactic sugar
To use decorators concisely and elegantly, Python provides a special decorator syntax that replaces the decorator function name = outer by adding a single line @outer directly above the decorator object. Outer is called when the interpreter executes to @outer. The function name directly below it is passed in as an argument, and the result returned is reassigned to the original function name.
import time
def outer(function) :
def wrapper(*args, **kwargs) :
start_time = time.time() # current time
res = function(*args, **kwargs) # here is the argument
end_time = time.time()
print(end_time - start_time)
return res Returns the return value of the decorated object
return wrapper
@outer
def func(x, y) :
time.sleep(3) Can sleep the application for 3s
print(x, y)
return x, y
Copy the code
Parameterized decorator implementation
To implement the authentication decorator, you need to determine the user name and password from where? Like files or databases; With authentication function; The code of the decorated object can be executed only after the authentication is passed; The outer function can only take one argument, the memory address of the decorated function. So the implementation code is as follows
def auth(db_type) :
def outer(func) : The index db_type can only be obtained from an external function due to the syntactic sugar limit of passing only one parameter
def wrapper(*args, **kwargs) :
user = input('name').strip()
pwd = input('pwd').strip()
if db_type == 'file':
print('I'm logged in based on a file')
if user == 'egon' and pwd == '123':
res = func(*args, **kwargs)
return res
else:
print('Login failed')
elif db_type == 'mysql':
print('I'm logging in based on database MSQL')
else:
print('I don't support this type')
return wrapper
return outer
@auth('file') # place the return result of auth('file') after @outer
def index(x, y) :
print(x, y)
Copy the code
The reference decorator summary
Through the above code can be summed up the reference decorator universal template and the principle of the reference decorator.
defParamedic decorator (x,y,z) :
def outer(func) :
def wrapper(*args, **kwargs) :
res = func(*args, **kwargs)
return res
return wrapper
return outter
@ Parametrized decorator (1,y=2,z=3) # execute the parameterized decorator (x, y, z), place the return value after @, and get the parameterless decorator @outer.
defDecorated object ():
pass
Copy the code
wraps
The basic principle of decorators is to make the Wrapper function exactly the same as the original one: do not change the source code, do not change the way it is called, but add other functions. The decorator template summarized above has a slight flaw. For example, printing the name of the decorated function or the decorated function’s documentation will reveal that the function being called is not the original function.
import time
def timer(func) :
def wrapper(*args,**kwargs) :
start_time = time.time()
print('hello')
time.sleep(2)
end = time.time()
print(end-start_time)
res = func(*args,**kwargs)
return res
return wrapper
@timer
def index(x,y) :
Doc: I'm index :param x: :param y: :return:"
print(x,y)
index(1.2)
print(index.__name__) Look at the function name, wrapper
print(index.__doc__) # Look at the function specification document None
Copy the code
How to solve this problem? You can refer to the following two methods:
- the way aAssign attributes of the original function to the Wrapper function manually
Function wrapper.__name__ = function.__name__
# 2, function wrapper.__doc__ = function
# wrapper.__name__ = func.__name__
# wrapper.__doc__ = func.__doc__Disadvantages: Each function has many attributes, do you really need to change them one by one? - Wraps: This is essentially a wrapper that assigns all the attributes of the original function to the Wrapper functionfrom functools import wraps
def outer(func) :
# Pass the wraps function as an argument
@wraps(func)
def wrapper(*args,**kwargs)
res = func(*args,**kwargs)
return res
return wrapper
Copy the code