Chapter 7 function decorators and closures

Keywords: Python Pycharm

  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

  1. Can replace decorated functions with other functions.
  2. 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.

Posted by Cerberus_26 on Sat, 18 Sep 2021 01:34:57 -0700