Closures and decorators

Keywords: Python

1, Closure

1. Definition

A closure is a function that extends to a function that contains references in the body of the function definition, but non global variables that are not defined in the body of the definition. It doesn't matter whether a function is anonymous or not. The key is that it can access non global variables defined outside the definition body.

A closure is a function that preserves the binding of free variables that exist when you define a function, so that when you call a function, you can still use those variables even though the definition scope is not available. (only functions nested in other functions may need to handle external variables that are not acting globally in.)

Human voice: closure is to get different results according to different configuration information (code example).

A closure is an entity composed of a function and its associated reference environment.

2. Code example

def fo_out(int_out):
    def fo_in(int_in):
        return int_in + int_out
    return fo_in
 
a = fo_out(1)
b = fo_out(1000)
 
print a(1)
print b(1000)

//Output:
2
2000

3. Precautions

  • If the variable outside the definition body is of variable type (such as list), you can use append to pass the value.

    def make_averager():
        series=[]
        
        def averager(new_value):
            series.append()
            total=sum(series)
            return total/len(series)
           
        return averager
  • If the variables outside the definition body are immutable types (numbers, strings, tuples, etc.), you can declare nonlocal in the definition body to make it a free variable.

    def make_averager():
        count=0
        total=0
        
        def averager(new_value):
            nonlocal count,total
            count += 1
            total += new_value
            return total/count
           
        return averager

OK, let's put the closure aside and look at the decorator again?

2, Decorator

1. Definition

A decorator is a callable object whose arguments are another function (the decorated function). The decorator may process the decorated function and return it, or replace it with another function or callable object.

Typical behavior: replace the decorated function with a new function, both of which take the same parameters, and (usually) return the value that the decorated function should have returned, while doing some additional operations.

2. Code example

@clock
def factorial(n):
    return 1 if n<2 else n*factorial(n-1)

The above is equivalent to:

def factorial(n):
    return 1 if n<2 else n*factorial(n-1)

factorial=clock(factorial)

It can be seen that the decorator is actually a simple way to write closures!

3. When does the decorator run?

registry = []


def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func


@register
def f1():
    print('running f1()')


@register
def f2():
    print('running f2()')


if __name__ == '__main__':
    print(u'Here we go')
    f1()

The operation result is:

running register(<function f1 at 0x000001F93DED7678>)
running register(<function f2 at 0x000001F93DED7708>)
//Here we go
running f1()

Conclusion: even if the above code is import ed as a module in other py files, the decorator will run immediately! The decorated function, on the other hand, works normally only when it is explicitly called.

4. Decorators commonly used in the standard library

  • lru_cache (note function decorator)

    definition

    functools.lru_cache is a very practical decorator, which realizes the function of memo. This is an optimization technique, which saves the results of time-consuming functions and avoids repeated calculation when the same parameters are passed in.

    example
    import functools
    import time
    
    
    def clock(func):
        def clocked(*args):  #Note: single *: import all parameters as tuples
            t0 = time.perf_counter()
            result = func(*args)
            elapsed = time.perf_counter() - t0
            name = func.__name__
            arg_str = ','.join(repr(arg) for arg in args)
            print('[%0.8fs]%s(%s) ->%r' % (elapsed, name, arg_str, result))
            return result
        return clocked
    
    @functools.lru_cache()
    @clock # Other decorators
    def fibonacci(n):
        if n < 2:
            return n
        return fibonacci(n-2) + fibonacci(n-1)
    
    if __name__=='__main__':
        print(fibonacci(6))
  • Single dispatch

    definition

    @The single dispatch decorator can split the whole scheme into multiple modules, and even provide special functions for classes that you cannot modify. Ordinary functions decorated with @ singledispatch become pan functions: a set of functions that perform the same operation in different ways according to the type of the first parameter.

To be continued...

Posted by JamesyBHOY on Sat, 06 Jun 2020 03:24:53 -0700