Decorative closure with Python

Variable scoping rules

First, in the function you are able to access the global variables:

>>> a = 'global var'

>>> def foo():
	print(a)

>>> foo()
global var

Then, in a nested function, the inner functions can access the local variables defined in the outer function:

>>> def foo():
	a = 'free var'
	def bar():
	    print(a)
	return bar

>>> foo()()
free var

Closure

The above function is nested closure. Closure means extending the scope of the function, which can be accessed in a non-global variable is not defined in the body of the function definition. Non-global variable is not defined in the body of the function definition is generally present in a nested function.

The above example is a variable is not defined in a non-global variable function bar. For the bar, it has a professional name, called free variables .

The name of free variables can be viewed in the bytecode object:

>>> bar = foo()
>>> bar.__code__.co_freevars
('a',)

The value of the free variable bindings in __closure__ property function of:

>>> bar.__closure__
(<cell at 0x000001CB2912DF48: str object at 0x000001CB291D3D70>,)

Wherein the stored object corresponding to the sequence of cell free variables, cell_contents cell object attribute holds the value of the variables:

>>> bar.__closure__[0].cell_contents
'free var'
>>> a = 1
>>> def foo():
	print(a)
	a += 1

>>> foo()
UnboundLocalError: local variable 'a' referenced before assignment

>>> def foo():
	a = 1
	def bar():
	    print(a)
	    a += 1
	return bar

>>> foo()()
UnboundLocalError: local variable 'a' referenced before assignment

In both cases, an error will be. This is not a defect, but rather design choices Python. Python is not required to declare variables, but will assume variable assignment in the function definition body is a local variable, to avoid to modify global variables in unknowingly.

a += 1The a = a + 1same, when the compiled function definition body, will as a local variable, not as free variables saved. Then try to get the value of a found a not binding value, then an error.

The solution to this problem, one is placed in a variable number of variable objects such as lists, dictionaries:

def foo():
    ns = {}
    ns['a'] = 1
    def bar():
        ns['a'] += 1
        print (ns['a'])
    return bar

Another method is to use global or nonlocal variable declared as global variables or free variables:

>>> def foo():
	a = 1
	def bar():
	    nonlocal a
	    a += 1
	    print(a)
	return bar

>>> foo()()
2

When the free variables subject itself is variable, it can be operated directly:

def make_avg():
    ls = []
    def avg(x):
        ls.append(x)
        print(sum(ls)/len(ls))
    return avg

Decorator

Decoration is callable object parameters are typically another function. It may be decorated to enhance the decorative function behaves in some way, and then returns the function to be decorated or to replace it with a new function.

One of the most simple without any additional action decorator:

def decorate(func):
    return func

decorateFunction is a simple decorator, use:

def target():
    pass

target = decorate(target)

Python provides a syntactic sugar for the decorator, and can easily be written as:

@decorate
def target():
    pass

Run import

Decoration is a very important feature is that it is introduced (block loading) run:

def decorate(func):
    print('running decorator when import')
    return func

@decorate
def foo():
    print('running foo')
    pass

if __name__ == '__main__':
    print('start foo')
    foo()

result:

running decorator when import
start foo
running foo

We can see, decorators are running when importing, and was decorated function is run when explicitly called.

Decoration may return the function itself to be decorated, and runtime characteristics of the introduced combination, can achieve a simple registration function:

view_registry = []

def register(func):
    view_registry.append(func)
    return func

@register
def view1():
    pass

@register
def view2():
    pass

def main():
    print(view_registry)


if __name__ == '__main__':
    main()

Returns a new function

Examples of the decorator are returned to be decorated original function, but the typical behavior of the decorator or return a new function: to replace the decorative function to a new function, a new function takes the same function of parameters of the original, and return to the original function the value returned by this. Similar wording:

def deco(func):
    def new_func(*args, **kwargs):
        return func(*args, **kwargs)
    return new_func

In this case the decoration is on the use of closures. JavaScript function in the stabilization and the throttle is this typical behavior decorator. The new function will generally use an external decorative variables as a function of free variables, make some kind of enhanced behavior function.

For example, we know that when Python function parameter is a variable object, will produce unexpected behavior:

def foo(x, y=[]):
    y.append(x)
    print(y)

foo(1)
foo(2)
foo(3)

Output:

[1]
[1, 2]
[1, 2, 3]

This is because the default value of the function is stored in __defaults__ property, point to the same list:

>>> foo.__defaults__
([1, 2, 3],)

We can remove with a decorator before the function performs the default value so deep copy, and then overwrite the original default parameter values ​​function:

import copy

def fresh_defaults(func):
    defaults = func.__defaults__
    def deco(*args, **kwargs):
        func.__defaults__ = copy.deepcopy(defaults)
        return func(*args, **kwargs)
    return deco

@fresh_defaults
def foo(x, y=[]):
    y.append(x)
    print(y)

foo(1)
foo(2)
foo(3)

Output:

[1]
[2]
[3]

Reception parameters decorator

Decorator acceptable addition as a function of parameters, other parameters can also be accepted. The method is used: to create a decorative factory, accepts and returns a decorator, then apply it to the function to be decorated, the following syntax:

def deco_factory(*args, **kwargs):
    def deco(func):
        print(args)
        return func
    return deco

@deco_factory('factory')
def foo():
    pass

In Web framework, typically you want to view the URL pattern mapping function that generates the response, and view function registered to some central registry. Before we ever realized in a simple registration decorators, only registered view function, but no URL mapping, it is not enough.

In Flask, the registered view function requires a decorator:

@app.route('/hello')
def hello():
    return 'Hello, World'

Principle is to use a decorator factory, you can achieve a simple analog look:

class App:
    def __init__(self):
        self.view_functions = {}

    def route(self, rule):
        def deco(view_func):
            self.view_functions[rule] = view_func
            return view_func
        return deco
         
app = App()

@app.route('/')
def index():
    pass

@app.route('/hello')
def hello():
    pass

for rule, view in app.view_functions.items():
    print(rule, ':', view.__name__)

Output:

/ : index
/hello : hello

You may also be used to determine plant decorator view function which allows the HTTP request method:

def action(methods):
    def deco(view):
        view.allow_methods = [method.lower() for method in methods]
        return view
    return deco
         
@action(['GET', 'POST'])
def view(request):
    if request.method.lower() in view.allow_methods:
        ...

Overlapping decorator

Decorator can overlap is used:

@d1
@d2
def foo():
    pass

Equivalent to:

foo = d1(d2(foo))

Class decorator

Decorator parameters may also be a class, that is, decorator can decorate categories:

import types

def deco(cls):
    for key, method in cls.__dict__.items():
        if isinstance(method, types.FunctionType):
            print(key, ':', method.__name__)
    return cls

@deco
class Test:
    def __init__(self):
        pass

    def foo(self):
        pass

Guess you like

Origin www.linuxidc.com/Linux/2019-07/159657.htm