python closures and decorators

python closures and decorators

1, What is a closure?

1. Scope of function

First of all, let's understand the scope of a variable in python. python has its namespace for each variable. A function can only call the global global variable or its own local variable. With this in mind, what is a closure.

2. What is a closure?

Simply put, a closure is a function dynamically generated and returned by other functions. Its key property is that the returned function can access variables in the local namespace of its creator (the creator here generally refers to the outermost function).
Generally speaking, a closure is a function that retains the binding of free variables when defining a function. In this way, when calling a function, the closure can still retain those bindings although the definition scope is unavailable. The difference between a closure and a normal function is that a closure can continue to access the local namespace of its creator even after the creator has executed it.

3. Function of closure

Closures can avoid using global values and provide some form of data hiding. It can also provide object-oriented solutions to problems. Closures can provide an alternative and more elegant solution when there are few methods (in most cases, a method) in a class. However, when the number of properties and methods becomes larger, it is better to implement a class.

4. Take two examples of closures

# Closures: functions that return functions
# The function of closure is that the accessed function can access variables in the local namespace of its creator
def make_closure(a):
    def closure(b):
        print('I know the variable a of outer function is %d'%a)
        print('The answer of plus is', a + b)
    return closure

closure = make_closure(10)
closure(3)
I know the variable a of outer function is 10
The answer of plus is 13

In this example, our closure can accept a variable of the external function and print it. The internal function does not perform any function for the time being. When the internal function is executed separately, it still retains the binding of the free variable a of the external function, which is a simple closure.

# Find any power of any number
def make_multiple_of(n):
    def multiple(x):
        print('The times is {}'.format(n))
        return x ** n
    return multiple

time3 = make_multiple_of(3)
time5 = make_multiple_of(5)

print(time3(2))
print(time5(2))
The times is 3
8
The times is 5
32

In this example, the multiple function accepts make_ multiple_ The power specified by the of function remains make when the multiple function is executed alone_ multiple_ Of, which completes the function of finding the power.

5. Closed__ closure__ attribute

Closures have one more closure attribute than ordinary functions, which records the address of free variables.

print(time3.__closure__)
(<cell at 0x0000028A02079D00: int object at 0x00007FFC9D241F40>,)

2, Decorator

1. Ordinary decorator

In fact, implement special methods__ call__ Any object of () is called callable. Therefore, in the most basic sense, decorators are callable and can return callable. Basically, the decorator receives a function, adds some functions, and returns.

As mentioned above, in python, everything is an object, and functions are no exception, so we can assign another variable to the function to make the two variables point to the reference of the same function.

First define a simple decorator

# A decorator
def make_wrapper(func):
    def wrapper():
        print('func has been decorated')
        func()
    return wrapper

def ordinary():
    print('This is an ordinary function')

In order to use our defined decorator, we can call:

ordinary = make_wrapper(ordinary)

In this way, we actually use closures to package and retain the binding of some free variables and assign the order function to ourselves. However, the order function at this time has more variable binding with the initial function, so we can call it like the previous order function.

ordinary()
func has been decorated
This is an ordinary function

2. Decorator grammar sugar

We can use @ wrapper_name instead of ordinary = make_ A complex assignment process such as wrapper (order) can make the code more concise.

def make_wrapper(func):
    def wrapper():
        print('func has been decorated')
        func()
    return wrapper

@make_wrapper
def ordinary():
    print('This is an ordinary function')

ordinary()
func has been decorated
This is an ordinary function

3. Standard timing decorator template

Here is a decorator that can calculate the running time of a function, providing an idea of writing a decorator.

import time
from functools import wraps

def time_wrapper(func):
    @wraps(func)  # This wrap decorator is built in python and is mainly used to copy function names, comment documents and other functions
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print("It takes {:2f} second".format(time.time() - start))
        return result
    return wrapper
    
@time_wrapper
def factorial(n):
# Calculate factorial
    result = 1
    for i in range(1, n + 1):
        result = result * i
    return result

4. Summary

The typical behavior of decorators can be summarized as: accepting and binding some free variables through closures, replacing the decorated function with a new function. They accept the same parameters, and (usually) return the value that should be returned by the decorated function, as well as some additional operations.

functools. The wraps decorator can copy the related attributes of the decorated function from func to the decorator.

3, Decorator factory function (parameterized decorator)

Python passes the decorated function as the first parameter to the decorator function. How can the decorator accept other functions? The answer is: create a decorator factory function, pass parameters to it, return a decorator, and then apply it to the function to be decorated.

Because the decorator takes parameters, we also need to take parameters when reusing syntax sugar

Generally speaking, decorators need two layers of function nesting. Since decorator factory functions need to accept parameters, decorator factory functions need three layers of function nesting.

# A function registration decorator, which is convenient to view the registered functions
registry = set()
def register(active=True):
    def decorator(func):
        print('runing registry(active=%s) -> wrapper(%s)' %(active, func))
        if active:
            registry.add(func)
        else:
            registry.discard(func)

        # This is a decorator
        def wrapper(*args, **kwargs):
            print('do something')
            result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@register(active=False)
def func1():
    print('runing func1')

@register(active=True)
def func2():
    print('runing func2')

@register(active=True)
def func3():
    print('runing func3')
runing registry(active=False) -> wrapper(<function func1 at 0x0000028A020AC280>)
runing registry(active=True) -> wrapper(<function func2 at 0x0000028A020AC820>)
runing registry(active=True) -> wrapper(<function func3 at 0x0000028A020ACDC0>)

Check which functions are registered. Func1 is not registered, so there is no func1 in the result

4, Reference

http://c.biancheng.net/view/5335.html
https://www.yiibai.com/python/closure.html
https://www.yiibai.com/python/decorator.html
<FluentPYthon>

Keywords: Python Back-end

Added by alapimba on Wed, 23 Feb 2022 16:31:02 +0200