A decorator is a callable that takes another functions as argument (the decorated function). The decorator may perform some processing with the decorated function, and returns it or repalces it with another function or callable object.
Strictly speaking, decorators are just syntactic sugar. you can always simply call a decorator like any regular callable, passing another function.
registry = 
Test in python 2.9 and python 3.5, it is different with the author said as the decorators is runing right after the decorated function is defined in books(page 185, 186). the decorators is running just before the decorated funtion is running.
Most decorators do not change the decorated function, they usually do it by defining an inner function and returning it to replace the decorated fucniton.
b = 6
Python compiles the body of the function, it decides that b is a local variable because it is assined within the function.
When call f2(3), the body of f2 fetches and prints the value of the local variable a, but when trying to fetch b, it discoves that b is unbound.
This is a design choice: python does not require you to declare variables, but assumes that a variable assigned in th body of function is local.
If you want the interpreter to treat b as gloabl variable in spite of the assignment within the function, you can use the gloabl declaration.
b = 6
A closure is function with extended scope that encompasses non-global variables referenced in the body of the function but not defined there. It does not matter whether the function is anonymous or not, what matters is that it can access non-global varialble that are defined outside its body.
Closures are functions that retains the bindings of the free varaibles that exist when the function is defined, so that they can be used later when the functions is invoked and the defining scope is no longer available.
The problem is that the statement count += 1 actually means the same as count = count + 1, when count is a number or any immutable type. So we are actually assigning to count in the body of avg, and that makes it local variable. The same problem affacts the total variable.
To save the above problem, the nonlocal declaration was introduced in python 3. It lets you flag a variable as a free variable even when it is assigned a new value within the function. If a new value is assigned to a nonlocal variable, the binding stored in the closure is changed.
# python 3.5
Python has three built-in functions that are designed to decorate methods: property, classmethod and staticmethod.
Another frequently seen decorator is functools.wraps, a helper for building well-behaved decorators.
functools.lru_cache is very practical decorator that implements memoization: an optimization technique which works by saving the results of previous invocations of an expensive function, avoiding repeat computations on previously used arguments.
lru_cache can be tuned by passing two optional arguments. It’s full signature is:
The maxsize arguments determines how many call results are stored. After the cache is full, older results are discarded to make room. For optimal performance, maxsize should be a power of 2. Type typed argument, if set to True, stores results of different argument types separately.
The new functools.singledispatch decorator is python 3.4 allows each module to contribute to the overall solution, and lets you easily provide a specialized function even for classes that you can’t edit. If you decorate a plain function with @singledispatch it becomes a generic functions: a group of functions to perform the same operation in different ways, depending on the type of the first argument.
# python 3.5
@singledispatch: register the specialized functions to hanlde ABCs (abstract classes) such as numbers.Integral and abc.MutableSequence instead of concrete implementations like int and list. This allows your code to support a greater variety of compatible types. For example, a python extension can provide alternatives to the int type with fixed bit lengths as subclasses of numbers.Integral.
A notable quality of the singledispatch mechanism is that you can register specialized functions anywhere in the system, in any module.
When two decorators @d1 and @d2 are applied to a function f in that order, the result is the same as f = d1(d2(f))
When parsing a decorator in source code, python takes the decorated function and passes it as the first argument to the decorator function. How to make a decorator accept other arguments? The answer is: make a decorator factory that takes those arguments and returns a decorator, which is then applied to the function to be decorated.
registry = set()
In order to make it easy to enable or disable the function registration performed by register, an optional active parameter which, if False skips registering the decorated function.
As shown in above example, the new register function is not a decorator but a decorator factory. When called, it returns the actual decorator that will be applied to the target function.
instead of using the @ syntax, we used register as a regular function, the syntax ineeded to decorate a function f would be register()(f) to add f to the register(active=False)(f) to remove it.