Detailed explanation 2_ Decorator function python

0. Function nesting

Variable name resolution: LEGB principle

Variable name lookup:

  1. First, search from local (L);

  2. The local scope (E) of def or lambda in the upper level organization is not found;

  3. Find from global scope;

  4. Search from the built-in module (B) and find the first place;

Nonlocal keyword: if an internal function wants to change the variables of an external function, it needs to add the nonlocal keyword. An example is as follows

def outer():
    a = 100

    def inner():
        nonlocal a
        b = 200
        a += b
        print('I'm an internal function', a)

    inner()
    print(a)


outer()

1. Closure

1.1 concept

First, let's take a look at the explanation of closures on Wiki:

In computer science, closures (English: closures), also known as lexical closures or function closures, are functions that reference free variables. The referenced free variable will exist with the function, even if it has left the environment in which it was created. Therefore, there is another saying that a Closure is an entity composed of a function and its related reference environment. Closures can have multiple instances at runtime. Different reference environments and the same function combination can produce different instances.

1.2 conditions

Three conditions for forming closures:

  1. Nested function
  2. Internal functions refer to variables of external functions
  3. Return inner function
  • A simple example is as follows

    '''
    Find the correspondence on the line x of y Value, equation:
        y = a*x+b
    '''
    
    
    def outer(a, b):
    
        def inner(x):
            return a * x + b
    
        return inner
    
    
    line = outer(2, 1)
    print(line(1))
    

Simple analysis: viewing through breakpoints

  • At this time, the line value is: < function outer.. inner at 0x00000000031758b0 >

1.3 closure trap

First look at a piece of code: think about the results yourself

def my_func(*args):
    fs = []
    for i in range(3):
        def func():
            return i * i

        fs.append(func)
    return fs


fs1, fs2, fs3 = my_func()
print(fs1())
print(fs2())
print(fs3())

Will the result be 0 1 4? The result is not 4. Why?

  • Because in the function my_ The internally defined function before func returns is not a closure function, but an internally defined function. Of course, the variable defined in the parent function referenced by this internal function is not a free variable, but only a local variable in the current block.
  • Before the internally defined function func is actually executed, any change to the local variable j will affect the operation result of the function func

Correct writing:

def my_func(*args):
    fs = []
    for i in range(3):
        func = lambda _i=i: _i * _i
        fs.append(func)
    return fs


fs1, fs2, fs3 = my_func()
print(fs1())
print(fs2())
print(fs3())

2. Decorator

Because I haven't learned object-oriented yet, I won't introduce the content related to class decorator here, but only the content of Guangyu function decorator.

Now let's learn about the decorator by simulating the decoration of a new house. You bought a new house (blank house). Now we want to decorate the blank house.

The opening and closing principle is the most basic and important design principle in programming.

Basic introduction:

  1. A software entity such as classes, modules and functions should be open to extensions (for the provider) and closed to modifications (for the user). Build the framework with abstraction and extend the details with implementation.
  2. When the software needs to change, try to realize the change by expanding the behavior of the software entity, rather than by modifying the existing code.
  3. Other principles are followed in programming, and the purpose of using design patterns is to follow the opening and closing principles.

Examples are as follows:

def decorator(func):
    def wrapper():
        func()
        print('Painting')
        print('Lay the floor')
        print('Buy furniture')
        print('Hardbound repair room, you can check in~~~')

    return wrapper


@decorator
def house():
    print('Rough house...')


house()

So what is the execution order?

def decorator(func):
    print('decorator start ...')

    def wrapper():
        print('wrapper start ...')
        func()
        print('wrapper end ...')

    print('decorator end ...')
    return wrapper


@decorator
def func():
    print('Function execution...')


# func()

Execution results:

decorator start ...
decorator end ...

Execution sequence: load decorator - > Load original func - > execute decorator - > func. Now point to wrapper

  • When executing the decorator, pass the original func as an argument to the formal parameter of the decorator

2.1 with reference

Or take the house decorated above as an example. We are from the decoration team and need to know the house address.

def decorator(func):
    def wrapper(address):
        func(address)
        print('Painting')
        print('Lay the floor')
        print('Buy furniture')
        print('Hardbound repair room, you can check in~~~')

    return wrapper


@decorator
def house(address):
    print('The house is: {}It's a blank room...'.format(address))


house('Hangzhou West Lake')

With the business expansion, we should not only decorate the house, but also decorate the factory. When decorating the factory, we also need to know the area of the plant. The process of decorating a house is similar to that of decorating a factory, but the received parameters are different. Can we use the same decorator?

def decorator(func):
    def wrapper(*args):
        func(*args)
        print('Painting')
        print('Lay the floor')
        print('Buy furniture')
        print('Hardbound repair room, you can check in~~~')

    return wrapper


@decorator
def house(address):
    print('The house is: {} It's a blank room...'.format(address))


@decorator
def factory(address, area):
    print('Factory in: {} It is a blank house with a construction area of: {}'.format(address, area))


house('Hangzhou West Lake')
factory('Hangzhou West Lake', 100)

Similarly, if the parameter has a default value parameter, the formal parameter needs to add * * kwargs:

def decorator(f):
	pass
	def wrapper(*args, **kwargs):
		pass
		f(*args, **kwargs)
		pass
	pass
	return wrapper

2.2. With return value

Continue to take the decoration of the house as an example. Now we need to make a budget for the decoration of the house to see how much it costs for blank house + decoration.

def decorator(f):
    def wrapper(*args, **kwargs):
        ret = f(*args, **kwargs)
        print('Painting')
        print('Lay the floor')
        print('Buy furniture')
        print('Hardbound repair room, you can check in~~~')
        ret += 10000
        return ret
    return wrapper


@decorator
def house(address, area):
    print('The house is: {}It's a rough house, the measure of area:{}'.format(address, area))
    return 50000


cost = house('Hangzhou West Lake', 100)
print('Estimated cost:{}'.format(cost))

2.3 decorator with reference

Simple example:

def decrator(*dargs, **dkargs):
    def wrapper(func):
        def _wrapper(*args, **kargs):
            print("Decorator parameters:", dargs, dkargs)
            print("Function parameters:", args, kargs)
            return func(*args, **kargs)

        return _wrapper

    return wrapper


@decrator(1, 2, a=1, b=2)
def f():
    print('Function execution')


f()

Execution sequence: load decorator - > Load F - > execute decorator - > return wrapper - > execute wrapper - > put back_ wrapper,f points to_ wrapper

  • analysis
    • The returned wrapper is executed directly

General decorator parameters are rarely used.

2.4. Multiple decorators

def decorator_1(f):
    print('decorator_1 start')

    def wrapper(*args, **kwargs):
        print('wrapper_1 start')
        ret = f(*args, **kwargs)
        print('wrapper_1 end')
        return ret

    print('decorator_1 end')
    return wrapper


def decorator_2(f):
    print('decorator_2 start')

    def wrapper(*args, **kwargs):
        print('wrapper_2 start')
        ret = f(*args, **kwargs)
        print('wrapper_2 end')
        return ret

    print('decorator_2 end')
    return wrapper


@decorator_2
@decorator_1
def hello():
    print('hello python')


hello()

It can be seen that when multiple decorators decorate the same function, it will be a nested decoration result, that is, first execute a decorator close to the function, and then decorate the execution result with a decorator far from the function.

2.4 summary

Decorator function:

  • Import log
  • Statistics execution time
  • Preprocessing before executing a function
  • Cleanup function after function execution
  • Permission verification and other scenarios
  • cache

Generic function decorator format:

def decorator(f):
	pass
	def wrapper(*args, **args):
		pass
		ret = f(*args, **args)
		pass
		return ret
	pass
	return wrapper
	

Keywords: Python

Added by vponz on Wed, 01 Dec 2021 02:41:41 +0200