# 5. Advanced usage of functions

## 5.1 local functions (nested functions)

python allows functions to be defined in the function body. The functions defined in the function body are called local functions, also known as nested functions.

```def outer_func():
name = "Meng"
def inner_func():
print(name)
inner_func()
outer_func()
```

## 5.2 higher order function

A function can be passed to another function as a parameter, or the return value of a function is another function. If one of them is satisfied, it is a high-order function.

```# example
# Function is passed as an argument to another function
def get_two_sum(x, y):
return x + y
def print_two_sum(func, a, b):
result = func(a, b)
print("The result of the two numbers is:", result)
print_two_sum(get_two_sum, 10, 20)
#======output========
The result of the two numbers is: 30
# Function as return value
def returned_func():
print("returned_func Function called")
def outer_func():
print("outer_func Function called")
return returned_func
ret = outer_func()
ret()
#=======output=======
outer_func Function called
returned_func Function called
```

Common high-order functions include map(), filter(), reduce(), which are often used together with lambda expressions in the following ways:

map() function

The function receives two parameters, a function and a sequence. Its function is to process the values in the sequence and return them to the sequence in turn. Its return value is an iterator object.

```list1 = [1, 2, 3, 4]
list2 = list(map(lambda x: x**2, list1)) # Change the elements in the list into the square of the corresponding elements. It is also possible to pass in a tuple and string for the second parameter here. If the dictionary type is passed in, it needs to be changed into dict_items type
print(list2)
#========output========
[1, 4, 9, 16]
```

filter() function

The function also receives two parameters, a function and a sequence. Its function is filtering. Its return value is also an iterator object.

```list1 = [1, 2, 3, 4]
list2 = list(filter(lambda x: x % 2 == 0, list1)) # The filtered elements are those whose return value is True
print(list2)
#=======output========
[2, 4]
```

reduce() function

The function also receives three parameters, one is a function, one is a sequence, and another optional parameter is an initial value. Its function is to accumulate the elements in the parameter sequence, and its return value is a value. It is not a built-in function, but a function in the functools module.

```from functools import reduce
list1 = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, list1, 10)
print(result)
#=======output=======
20
# The calculation process is as follows
# 10 + 1 = 11
# 11 + 2 = 13
# 13 + 3 = 16
# 16 + 4 = 20
```

## 5.3 closure of function

Using nested functions, an internal function is defined in the external function. The internal function uses the temporary variables in the external function, and the return value of the external function is the reference of the internal function, which forms a closure.

```def outer_func(a):
b = 10
def inner_func():
return a + b
return inner_func
ret = outer_func(3)
print(ret())
```

## 5.4 function decorator

The decorator is essentially a Python function, which allows other functions to add additional functions without any code changes. The return value of the decorator is also a function object.

Decorator = higher order function + function nesting + closure

### 5.4.1 nonparametric function decorator

The first is the simplest decorator. The function to be decorated does not need additional parameters, such as testing the execution time of a function:

```import time
def test_run_time(func): # Function as a parameter to form a higher-order function
def wrapper(): # Functions are defined inside functions to form function nesting
start_time = time.time()
func() # Use outer function variables to form closures
end_time = time.time()
print("The function run time is:", end_time - start_time)
return wrapper # Function as the return value to form a higher-order function
@test_run_time  # This syntax is equivalent to test = test_run_time(test) simplifies the operation and omits an assignment statement.
def test():
time.sleep(1)
print("Here is the function to be tested")
test()
```

### 5.4.2 parametric function decorator

```import time
def test_run_time(func):
def wrapper(*args): # If the function to be decorated does not need to pass keyword parameters, just write * args. If keyword parameters are required, add * * kwargs
start_time = time.time()
func(*args)
end_time = time.time()
print("The function run time is:", end_time - start_time)
return wrapper
@test_run_time
def print_two_sum(a, b):
time.sleep(1)
print("{}+{}={}".format(a, b, a+b))
print_two_sum(5, 7)
#=========output=========
5+7=12
Function running time: 1.0009658336639404
```

### 5.4.3 function decorator with return value

```import time
def test_run_time(func):
def wrapper(*args):
start_time = time.time()
result = func(*args)
end_time = time.time()
print("The function run time is:", end_time - start_time)
return result
return wrapper
@test_run_time
def get_two_sum(a, b):
time.sleep(1)
return a + b
result = get_two_sum(5, 7)
print("The sum of the two numbers is:", result)
#=======output=======
Function running time: 1.0006444454193115
The sum of the two numbers is 12
```

According to the above three examples, we can summarize a complete decorator frame as follows:

```def wrap(func):
def wrapper(*args, **kwargs):
"""Functions can be added"""
result = func(*args, **kwargs)
"""Functions can be added"""
return result
return wrapper
```

When using, you only need to add @ wrap in front of the defined function.

### 5.4.4 decorator with parameters

Two layers of nested functions are used, which is equivalent to encapsulating the original decorator and returning a decorator.

```import time
def cal(mark):
def wrapper(func):
def deco(*args):
if mark == "+":
print("This is an addition operation")
elif mark == "*":
print("This is multiplication")
start_time = time.time()
result = func(*args)
end_time = time.time()
print("{}Calculation time:".format(mark), end_time - start_time)
return result
return deco
return wrapper

@cal("+")	# This syntax is equivalent to cal = cal ("+") get_ two_ sum = cal(get_two_sum)
def get_two_sum(a, b):
time.sleep(1)
return a + b

@cal("*")
def get_two_multi(a, b):
time.sleep(2)
return a * b
result = get_two_sum(5, 7)
print("The sum of the two numbers is:", result)
print('-' * 20)
result1 = get_two_multi(5, 7)
print("The product of the two numbers is:", result1)
#===========output=========
This is an addition operation
+Operation time: 1.0009543895721436
The sum of the two numbers is 12
--------------------
This is multiplication
*Operation time: 2.0008320808410645
The product of the two numbers is 35
```

## 5.5 partial function

When a function has many parameters, the caller needs to provide multiple parameters. If the number of parameters is reduced, the burden on the caller can be simplified. Using partial function, one or some parameters in the function can be fixed, so that when the same parameters need to be passed, one parameter can be written less to reduce the burden. This function is realized by calling partial in functools.

```# example
# For example, to convert multiple binary strings into numbers, we can do the following operations to save the trouble of passing in the base parameter every time
from functools import partial
int2 = partial(int, base=2)
print(int2("11001"))
```

## 5.6 generator functions

Generator function is a special function, which is a tool used to create generator objects. The generator function uses the yield statement to return. Its return value is a generator object. The yield statement returns one result at a time. In the middle of each result, suspend the state of the function so that it can continue to execute where it left next time.

```# Generating Fibonacci sequence using generator
def fib(num):
a, b = 0, 1
for i in range(num):
yield b
a, b = b, a+b
for i in fib(10):
print(i, end=" ")
#========output=======
1 1 2 3 5 8 13 21 34 55
```

The generator can only traverse once

Generator Expressions

Similar to list derivation, however, the generator returns one object that produces results on demand, rather than building a list of results at a time.

```# List derivation
list1 = [i for i in range(0, 10, 2)]
for i in list1:
print(i)
# After traversing the for loop, you can still access it
print(list1)

# Generator Expressions
gen = (i for i in range(0, 10, 2))
for i in gen:
print(i)
# After traversing with the for loop, you can access a single element in it, which cannot be accessed
print(gen) # TypeError: 'generator' object is not subscriptable
# This means that the generator expression is destroyed after one call
```

Although generators and iterators are similar in use, their internal principles are completely different.

1. Iterator is that all the contents are in memory, and use the next function to traverse down

2. The generator will not put the content in memory. Each time the next function is called, the calculated element will be returned and destroyed immediately after use.

Therefore, the use of generators and iterators can be measured in time and space. If it is a program with a large amount of data, use generators, but if it is time first, iterators can be given priority.

Reference from blog https://blog.csdn.net/qq_20880939/article/details/100899942

# 6. Built in functions in Python

Click the corresponding function name to view the corresponding usage.

# 7. Others

## 7.1 specifying and checking function parameters and return value types

Python has added the specification and check of parameter and return value types after version 3.5. When creating a new variable, you can also specify the type.

### 7.1.1 designation of basic types

```def test(a: str) -> list:
return a

test([1, 2, 2])
```

For example, in the above function, we specify that the type of a is string and the return value type is list. In the code, we directly return a as the return value. When used in pycharm, it will give the corresponding color prompt, but there will be no error in actual operation, because Python is a dynamic language in essence.

### 7.1.2 complex assignment (assignment of set related types)

In addition to specifying the most basic data types, you can also specify collection related types.

Here you need to use some classes and methods in the typing module

```from typing import List
Vector = List[float] # This specifies that the elements in the List should be of float type. If the type of each element is specified, such as Tuple[float, int], then its length can only be 2. Moreover, the first element is of float type and the second element is of int type. Of course, it will only give a prompt in pycharm. The actual operation will not report an error because the check type does not match.
def scale(scalar: int, vector: Vector) -> Vector:
return [scalar * num for num in vector]
new_vector = scale(2, [1.0, 2.6, 5.4])
print(new_vector)
```

### 7.1.3 specifying generics

```from typing import Sequence, TypeVar, Union
T = TypeVar('T')      # Declare a type variable. If nothing is added after it, it can be of any type
def first(l: Sequence[T]) -> T:
return l
```

If you specify which types can be represented when declaring type variables, you will match them when checking. If they do not match, you will be prompted accordingly.

```T = TypeVar('T')  # T can represent any type
A = TypeVar('A', str, bytes)  # A must be a string or byte
```

### 7.1.4 type assignment when creating variables

```name:str = "Meng" # If it is written as name:str = 123, pycharm will give a prompt
print(name)
``` For more type assignments, please refer to typing in Python Documentation.

This part refers to the blog https://blog.csdn.net/sgyuanshi/article/details/96192887

Tags: Python

Posted by kalaszabi on Wed, 11 May 2022 23:54:18 +0300