function decorators are used to "mark" functions in the source code to enhance the behavior of functions in some way. This is a powerful feature, but to master it, you must understand closures.
the ultimate goal of this chapter is to explain the working principle of function decorators, including the simplest registration decorators and more complex parametric decorators. However, before achieving this goal, we would like to discuss the following topics:
- How Python computes decorator syntax
- How does Python determine whether a variable is local
- Why closures exist and how they work
- What problems can nonlocal solve
After mastering these basic knowledge, we can further explore decorators:
- Achieve a well behaved decorator
- Useful decorators in the standard library
- Implement a parametric decorator
Basics of decorators
decorators are callable objects whose parameters are another function (decorated function). The decorator may process the decorated function and then return it, or replace it with another function or callable object.
@decorate def func(): print('run func') # The above code is equivalent to def func(): print('run func') func = decorate(func)
the final results of the two methods are the same: the target obtained after the execution of the above two code fragments is not necessarily the original target function, but the function returned by modify (target).
>>> def deco(f): ... def inner(): ... print('run inner') ... return inner >>> @deco ... def target(): ... print('run target') ... >>> target() run inner >>> target <function deco.<locals>.inner at 0x000001F0CA6FC840> >>> # Call the decorated target to actually run inner # target is now a reference to inner
strictly speaking, decorators are just sugar. As shown earlier, the decorator can be called like a regular callable object, and its parameter is another function. Sometimes, this is more convenient, especially when doing metaprogramming (changing the behavior of the program at run time).
To sum up, the two characteristics of the ornament are
- Can replace decorated functions with other functions.
- The decorator executes immediately when the module is loaded.
When does python execute decorators
A key feature of decorators is that they run immediately after the decorated function is defined. This is usually when importing (that is, when Python loads a module)
the function decorator executes immediately when importing the module, while the decorated function runs only when explicitly called. This highlights what Python programmers call the difference between import time and runtime.
Variable scope rule
b = 1 def fun1(a): print(a) print(b) b = 2 if __name__ == "__namin__": fun1('a') Output results: Traceback (most recent call last): File ".\test1.py", line 7, in <module> fun1('a') File ".\test1.py", line 4, in fun1 print(b) UnboundLocalError: local variable 'b' referenced before assignment # Use global variables and declare with global b = 1 def fun1(a): global b print(a) print(b) b = 2
when Python compiles the definition body of a function, it judges that b is a local variable because it is assigned a value in the function. The generated bytecode confirms this judgment, and python will try to get b from the local environment. When func(3) is called later, the definition of func will get and print the value of local variable a, but when trying to get the value of local variable b, it is found that b has no bound value.
closure
def mack_avg(): ls = [] def avg(n): ls.append(n) return num(ls) / len(ls) return avg avg = mack_avg() avg(10) avg(11) __code__ : Represents the compiled function determiner >>> avg.__code__.co_varnames ('n',) >>> avg.__code__.co_freevars ('ls',) # ============================ avg.__closure__ Corresponding to each element in avg.__code__.co_freevars A name in the. These elements are cell Object, there are cell_contents Property that holds the real value. >>> avg.__code__.co_freevars ('series',) >>> avg.__closure__ (<cell at 0x107a44f78: list object at 0x107a91a48>,) >>> avg.__closure__[0].cell_contents [10, 11, 12]
to sum up, a closure is a function that retains the binding of free variables existing when defining a function. In this way, when calling a function, although the definition scope is unavailable, those bindings can still be used.
Note that only functions nested in other functions may need to deal with external variables that are not in the global scope.
nonlocal declaration
its function is to mark the variable as a free variable. Even if a new value is assigned to the variable in the function, it will become a free variable. If a variable declared by nonlocal is given a new value, the binding saved in the closure is updated.
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count, total count += 1 total += new_value return total / count return averager
for immutable types such as numbers, strings and tuples, they can only be read and cannot be updated. If you try to rebind, for example, count = count + 1, the local variable count is implicitly created. In this way, count is not a free variable, so it will not be saved in the closure. If a variable is declared using nonlocal, the declared variable is given a new value, and the binding saved in the closure is updated.