# [Article 10] Closures and Decorators

## I. Closure

### 1.1 Closure Definition

Under the premise of function nesting, the internal function uses the variables of the external function, and the external function returns the internal function. We call this internal function which uses the variables of the external function closure.

### Composition of 1.2 Closure

Constitutive conditions:

• On the premise of function nesting (redefining function inside function)
• Internal functions use variables of external functions (including parameters of external functions)
• External functions return internal functions
``` 1 # Define an external function
2 def func_out(num1):
3     # Define an internal function
4     def func_inner(num2):
5         # Internal functions use variables of external functions(num1)
6         result = num1 + num2
7         print("The result is:", result)
8     # The external function returns the internal function, which is the closure.
9     return func_inner
10
11
12 # Create closure instances
13 f = func_out(1)
14 # Execution closure
15 f(2)
16 f(3)
17
18 # results of enforcement
19 # The result is: 3
20 # The result is: 4```

ps: From the output above, we can see that the closure preserves the variable num1 in the external function, and every execution of the closure is calculated on the basis of num1 = 1.

Function of closures: Closures can save variables in external functions and will not be destroyed as the external functions are called.

Note: Because closures refer to variables of external functions, variables of external functions are not released in time and memory is consumed.

### 1.3 __closure__

Judging whether a function is a closure function: Output _closure_ has cell element: is a closure function

``` 1 def func():
2     name = 'eva'
3
4     def inner():
5         print(name)
6     print(inner.__closure__)    # (<cell at 0x0000028DD6FD0E58: str object at 0x0000028DD70D1F48>,)
7     return inner
8
9
10 f = func()
11 f()
12
13
14
15
16 name = 'egon'
17
18
19 def func2():
20     def inner():
21         print(name)
22     print(inner.__closure__)    # The results are as follows: None，It's not a closure function, it's a reference to outsourcing scope, not a global scope.
23     return inner
24
25
26 f2 = func2()
27 f2()```

### 1.4 Modify Outsourcing Variables Used in Closures

Error examples:

``` 1 # Define an external function
2 def func_out(num1):
3
4     # Define an internal function
5     def func_inner(num2):
6         # The intention here is to modify the outside world. num1 The value of a local variable is actually defined in an internal function. num1
7         num1 = 10
8         # Internal functions use variables of external functions(num1)
9         result = num1 + num2
10         print("The result is:", result)
11
12     print(num1)
13     func_inner(1)
14     print(num1)
15
16     # The external function returns the internal function, which is the closure.
17     return func_inner
18
19
20 # Create closure instances
21 f = func_out(1)
22 # Execution closure
23 f(2)```

The right example:

``` 1 # Define an external function
2 def func_out(num1):
3
4     # Define an internal function
5     def func_inner(num2):
6         # The intention here is to modify the outside world. num1 The value of a local variable is actually defined in an internal function. num1
7         nonlocal num1  # Tell the interpreter that external variables are used here a
8         # Modify external variables num1
9         num1 += 10
10         # Internal functions use variables of external functions(num1)
11         result = num1 + num2
12         print("The result is:", result)
13
14     print(num1)
15     func_inner(1)
16     print(num1)
17
18     # The external function returns the internal function, which is the closure.
19     return func_inner
20
21
22 # Create closure instances
23 f = func_out(1)
24 # Execution closure
25 f(2)```

### Nesting of 1.5 closures

``` 1 def wrapper():
2     money = 1000
3     def func():
4         name = 'eva'
5         def inner():
6             print(name, money)
7         return inner
8     return func
9
10
11 f = wrapper()
12 i = f()
13 i()```

## II. Decorators

### 2.1 Definition of Decorator

A function that adds extra functionality to an existing function is essentially a closure function.

The characteristics of the decorator function:

• Do not modify the source code of existing functions
• Do not modify the way existing functions are called

### 2.2 Examples of Decorators

``` 1 # validate logon
2 def check(fn):
3     def inner():
5         fn()
6     return inner
7
8
9 def comment():
10     print("aa")
11
12
13 # Decorate functions with decorators
14 comment = check(comment)
15 comment()```

The basic rudiment of ornaments:

```def wrapper(fn):  # fn:objective function.
def inner():
'''Before the function is executed'''
fn() # Execute decorated functions
'''After the function is executed'''
return inner```

Explain:

• Closure functions have only one parameter and must be function types, so the function defined is the decorator.
• Coding should follow the principle of open and closed, which stipulates that the functional code that has been implemented is not allowed to be modified, but can be extended.

### 2.3 Grammatical Sugar

Python provides a simpler way to write decorative functions, that is, grammatical sugar. The writing format of grammatical sugar is @ decorator name. It can also decorate existing functions by grammatical sugar.

```import time

def timer(func):
def inner():
start = time.time()
func()
print(time.time() - start)
return inner

@timer   #==> func1 = timer(func1)
def func1():
print('in func1')

func1()```

Explain:

• @ timer is equivalent to func1 = timer(func1)
• The execution time of the decorator is executed immediately when the module is loaded.

### 2.4 General Decorator

#### 2.4.1 Decorative functions with parameters

``` 1 # Adding the function of output log
2 def logging(fn):
3
4     def inner(num1, num2):
5         print("--Efforts are being made to calculate--")
6         fn(num1, num2)
7
8     return inner
9
10
11 # Decoration function using decorator
12 @logging
13 def sum_num(a, b):
14     result = a + b
15     print(result)
16
17
18 sum_num(1, 2)```

### 2.4.2 Functions of Decorative Indefinite Length Parameters

``` 1 import time
2
3
4 # Calculate execution time
5 def timer(func):
6     def inner(*args, **kwargs):
7         start = time.time()
8         re = func(*args, **kwargs)
9         print(time.time() - start)
10         return re
11     return inner
12
13
14 # Using Grammatical Sugar Decoration Functions
15 @timer   # ==> func1 = timer(func1)
16 def func1(a, b):
17     print('in func1', a, b)
18
19
20 # Using Grammatical Sugar Decoration Functions
21 @timer   # ==> func2 = timer(func2)
22 def func2(a):
23     print('in func2 and get a:%s'%(a))
24     return 'fun2 over'
25
26
27 func1('aaaaaa', 'bbbbbb')
28 print(func2('aaaaaa'))```

#### 2.4.3 Decorate functions with return values

``` 1 import time
2
3
4 def timer(func):
5     def inner(*args, **kwargs):
6         start = time.time()
7         re = func(*args, **kwargs)
8         print(time.time() - start)
9         return re
10     return inner
11
12
13 @timer   # ==> func2 = timer(func2)
14 def func2(a):
15     print('in func2 and get a:%s' % a)
16     return 'fun2 over'
17
18
19 func2('aaaaaa')
20 print(func2('aaaaaa'))```

### Improvement of 2.4.4 Decorator

The decorator above is perfect, but the way we normally view some information about the function will fail here.

```1 def index():
2     '''This is a homepage information.'''
3     print('from index')
4
5
6 print(index.__doc__)    # Ways to view function annotations
7 print(index.__name__)   # How to view function names```

In order not to invalidate them, we need to add a little bit to the decoration to improve it:

``` 1 from functools import wraps
2
3
4 def deco(func):
5     @wraps(func)    # Added directly above the innermost function
6     def wrapper(*args, **kwargs):
7         return func(*args, **kwargs)
8     return wrapper
9
10
11 @deco
12 def index():
13     '''Ha ha ha ha'''
14     print('from index')
15
16
17 print(index.__doc__)    # Ha ha ha ha
18 print(index.__name__)   # index```

### Use of more than 2.5 ornaments

``` 1 def make_div(func):
2     """Return values for decorated functions div Label"""
3     def inner(*args, **kwargs):
4         return "<div>" + func() + "</div>"
5     return inner
6
7
8 def make_p(func):
9     """Return values for decorated functions p Label"""
10     def inner(*args, **kwargs):
11         return "<p>" + func() + "</p>"
12     return inner
13
14
15 # Decoration process: 1 content = make_p(content) 2 content = make_div(content)
16 # content = make_div(make_p(content))
17 @make_div
18 @make_p
19 def content():
20     return "Life is short and bitter"
21
22
23 result = content()
24
25 print(result)```

Note: Multiple decorators can decorate functions with multiple functions. The order of decoration is from inside to outside.

### 2.6 Decorators with Parameters

Decorator with parameters is to use the Decorator Decorative Function when you can pass in the specified parameters, grammatical format: @Decorator (parameters,...)

``` 1 # Adding the function of output log
2 def logging(flag):
3
4     def decorator(fn):
5         def inner(num1, num2):
6             if flag == "+":
8             elif flag == "-":
9                 print("--Efforts are being made to subtract calculations--")
10             result = fn(num1, num2)
11             return result
12         return inner
13
15     return decorator
16
17
18 # Decoration function using decorator
19 @logging("+")
21     result = a + b
22     return result
23
24
25 @logging("-")
26 def sub(a, b):
27     result = a - b
28     return result
29
30