# 1. related concepts

Take the following procedure as an example

```def add(a,b=0):   # a and B are formal parameters, which can define default values, such as b=0
c = a+b       # c is a local variable
global d, e   # d and e are global variables
d = a*b
e = a+b

x=8
y=9
add(x, y)  # x and y are actual parameters
```

## 1.1 formal parameters / actual parameters

Formal parameters: parameters written during function definition for which the system does not allocate memory space, but which can be used in the definition. For example: def(a, b=0).
Actual parameter: the variable passed to the function when the function is called. This is a variable that the system actually allocates memory space.

1) Formal parameters can define default values def func(a, b=5, c=10), but only those parameters at the end of the formal parameter table can have default parameter values. For example, def func(a, b=5) is valid, and def func(a=5, b) is invalid;
2) The parameter with default value should appear after the parameter list. If it appears before the parameter without default value, an error will be reported;

## 1.2 location parameters / keyword parameters

Positional parameter: the passed in values are assigned to the formal parameters of the function in order of position. For example, power(x, n),
Keyword parameter: a parameter assigned by name to a parameter, regardless of position. For example, func(25, c=24);

Benefits of using keyword parameters:
1) Don't worry about parameter order;
2) If other parameters have default values, we can assign values to only the parameters we want;

## 1.3 the number of parameters can be uncertain

### 1.3.1 use asterisk*

When defining a function, add a * sign before the parameter, and the parameter receives a tuple

```>>> def calc(*numbers):
...     s = 0
...     for n in numbers: # numbers is a tuple. You cannot use an asterisk to traverse it
...         s += n**2
...     return s
...
>>> L0 = [0,1,2,3]
>>>
>>> # When calling a function: add a * sign in front of list/tuple to change the list/tuple element into a variable parameter and pass it in
>>> calc(*L0)
14
>>> # Directly transfer a List. There is no problem in parameter parsing, but the List as a whole is not as expected
>>> # When adding parameters inside a function, it will be reported that list cannot be exponentiated with int.
>>> calc(L0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in calc
TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'
```

Asterisks cannot be used in normal statements

```>>> *L0
File "<stdin>", line 1
SyntaxError: can't use starred expression here
>>> a,b,c,d = *L0
File "<stdin>", line 1
SyntaxError: can't use starred expression here
>>> a,b,c,d = (*L0)
File "<stdin>", line 1
SyntaxError: can't use starred expression here
>>> (a,b,c,d) = (*L0)
File "<stdin>", line 1
SyntaxError: can't use starred expression here
>>>
```

Note:

1. *numbers can only be used in two statements: function parameter list and parameter passing when calling a function. Using asterisks in common statements (including function internal statements) will result in errors.
2. Used in the function parameter list: indicates that there are actually multiple parameters in this place, but they are used in a tuple within the function.
3. It is used when calling a function to pass parameters: it means that the List/Tuple to be passed is broken up and then passed, because the parameter of the function is not a List/Tuple but its element.
4. *numbers is written in a normal statement, and a syntax error will be reported: can't use starred expression here.

### 1.3.2 use double star**

Use * * kw to pass in 0 or more parameters with parameter names. These keyword parameters are assembled into a dict inside the function.
The function can be extended by using keyword parameters;

```>>> def person(name, age, **kw):
...    print('name:', name, ', age', age, ', others:' kw)
...
>>> person('Michael', 30) #Pass in keyword parameters only
name:Michael, age:30, others:{}
>>>
>>> person('Adam', 45, gender='M', job='Engineer') # The latter two parameters will be assembled into a dict.
name:Adam, age:45, others:{'gender':'M', 'job':'Engineer'}
>>>
>>> extra = {'gender':'M', 'job':'Engineer'}
>>> person('Adam', 45, **extra) #Add * * before dict to change dict into a keyword parameter;
name:Adam, age:45, others:{'gender':'M', 'job':'Engineer'}
```

### 1.3.3* Role with * *

Generally, * and * * are only used to pass parameters to a function, but they do not work well outside the function parameters (errors may also be reported)

For example:
foo(1, 2, 3) is equivalent to foo(*[1, 2, 3])
bar(a=1, b=2, c=3) is equivalent to bar(**{'a': 1, 'B': 2, 'C': 3})

*L0 can be considered as the result of splitting the list (tuple) l0.

*Function of: break up a list into elements.
L0 = [0, 1, 2, 3] \l0 is a list, a whole. Pass l0 to the function, and the function gets 1 parameter.
*L0 \l0 is 4 elements. When l0 is passed to the function, the function gets 4 parameters.

**To split the dict into a series of key value pairs.
D0 = {'a': 0, 'b': 1} \d0 is dict, which is directly passed to the function. The function gets 1 parameter.
D0 \y is two key value pairs. When d0 is passed to the function, the function gets 2 parameters.

## 1.4 Named keyword parameters

Use * * kw: you can pass keywords of any name as parameters;
Use *, city, job: you can restrict the name of keyword parameters. For example, only city and job can be accepted as keyword parameters;

1. The parameter name must be passed in when the corresponding parameter is transferred (the parameter is transferred in the way of 'city' ='Chengdu '). If there is no parameter name, it will be considered as a location parameter, which may lead to an Error;
2. If you give a default value when defining a function, you can call it without passing this parameter.
```>> def person(name, age, *, city, job):
..      print(name, age, city, job)
..
>> person('Jon', 30, city='Chengdu', job='Engineer') # Pass parameters by parameter name and operate normally;
on 30 Chendu, Engineer
>>
>> extra = {'city':'Shenzhen', 'job':'Art'}
>> person('Bill', 9, **extra) # Input parameters through dict, and normal operation;
ill 9 Shenzhen Art
>>>
>>> extra ={'job':'Art'}
>>> person('Bill', 9, **extra) # If the parameter is passed in through dict, an error will be reported: the city parameter is missing;
TypeError: person() missing 1 required keyword-only arg: 'city'
>>>
>>> extra = {'length':6, 'job':'Art'}
>>> person('Bill', 9, **extra) # If the parameter is passed in through dict, an error will be reported: the parameter length cannot be used;
TypeError: person() got a unexpected keyword arg: 'length'
```

## 1.5 Parameter combination

The five parameters of Python functions can be used in any combination. The order of parameter definitions must be:

1. Required parameter
2. Default parameters
3. Variable parameters
4. Named keyword parameters
5. Keyword parameter

For example:

```def f1(a, b, c=0, *args, **kw):
pass

def f2(a, b, c=0, *, d, **kw):
pass
```

Any function can be called in the form of func(*args, **kw) (a tuple and a dictionary), no matter how its parameters are defined.

## 1.6 Return value

1. Use return to return (jump out) from a function or return a value.
2. python programs return None by default
3. Return you can return multiple values (in fact, you can return a tuple, omit the parentheses, and directly assign values to multiple variables)
```def move(x0, y0, s=0, angle=0):
x1=x0+s*math.cos(angle)
y1=y0+s*math.sin(angle)
return x1,y1

x,y = move(100,100, 50, math.pi*(1/3))
```

## 1.7. DocStrings

1. You can embed a description statement in a function
2. Use three quotation marks to write descriptions across lines without line continuation
3. Use__ doc__ Method, which can be called to a description statement.
```>>> def max(x,y):
...     '''''Return the max of two number.
...     The two number must be integer.'''
...     if a>b:
...         return a
...     else:
...         return b
...
>>> print max(1,2)

>>> print max.__doc__
```

## 1.8 Function alias

The function name is actually a reference to a function, so you can assign the function name to a variable, and then call the function from this variable:

```>>> a = abs # Variable a points to abs function
>>> print(a(-1))
1
>>> print(a)
<built-in function abs>
```

## 1.9 Functional programming

Python provides partial support for functional programming;
Because Python allows the use of variables, Python is not a pure functional programming language;

# 2. higher order function

## 2.1 Variables can point to functions

```>>> x = abs(-10) # Call the function and obtain the function call result;
>>> f = abs      # Assign a function to a variable
>>> print(f)     # <built-in function abs>
>>> f(-10)       # The variable f points to abs, and f(-10) is exactly the same as abs(-10)
```

## 2.2 Function names are also variables

A function name is actually a variable that points to a function.
For example, abs() is a function. abs is a variable that points to the function that calculates the absolute value;

```>>> import builtins
>>> builtins.abs
<built-in function abs>
>>> builtins.abs = 10    # You can change the direction of the abs function
```

## 2.3 Incoming function

A function a can receive another function B as a parameter, and this function a is called a higher-order function;

```>>> def func_add(f, x, y):
...     return f(x)+f(y)
...
>>> abs_add(abs, 5, -6)
11
```

map(), reduce(), and are all high-order functions

## 2.4 Return function

In addition to accepting functions as parameters, higher-order functions can also return functions as result values;

# 3. anonymous function lambda

1. lambda defines an anonymous function
2. lambda will not improve the efficiency of the program, but make the code more concise.
3. lambda can only have one expression without writing return. The return value is the value of the expression;
4. If you can use for In If, never use lambda.
5. If you use lambda, do not include loops in lambda. If there are, you prefer to define functions to complete it, so that the code can be reused and better readable.
```>>> foo = [2,18,9,22]
>>> list(filter(lambda x:x%3==0, foo)) #The number in the list that can be divided by 3;
[18, 9]
>>> list(map(lambda x:x*2+10, foo)) #Make the original list elements *2+10;
[14, 46, 28, 54]
>>> list(map(lambda x:[x,x*2], foo)) #Map list elements to lists;
[[2, 4], [18, 36], [9, 18], [22, 44]]
>>> functools.reduce(lambda x,y:x+y, foo) # Returns a value to add the original list;
51
```

The above filter can be rewritten as: [x for x in foo if x%x==0]
The above map can be rewritten as: [x*2+10 for x in foo]

# 4. decorator

Function: enhance the function of the function without modifying the function definition itself;

Example 1:

Define a decorator that can print the name of the current calling function, accept a function as a parameter, and return a function

```
>>> def log(func):
...     def wrapper(*args, **kwargs):
...         print('call func: {name}: '.format(name=func.__name__))
...         return func(*args, **kwargs)
...     return wrapper
```

Apply the decorator (log) to other functions (now), which is equivalent to now=log(now):
Call now() to print a line of log before executing now itself

```>>> @log
... def now():
...     print(datetime.datetime.now())
...
>>> import datetime #You can import the modules it needs before calling now
>>> now()
call func: now
2018-07-27 11:29:18.239895
```

Example 2: define a decorator to print the number of function calls and function execution time

```import time
def timer(func):
lst = [0, 0] # Use a list to save the contents to be recorded. During each call, lst points to the same address;

def wrapper(*args, **kwargs):
start = time.time()
r = func(*args, **kwargs) #Call the function and keep the return value
end = time.time()
delta = end –start

lst += 1 # lst record the number of times the function is executed
lst += delta # lst record the execution time of the function

print(f'cnt: {lst}, time: {lst}, {delta}')

return r # Return the result of a function. There is a limitation here. You can only process functions that return a single value;

return wrapper # Returns a function with a shell
```

Test:

```>>> @timer
>>> def my_sum(n): #Define a function
...     r = 0
...     for i in range(n):
...         r += i
...     return r
...
>>> for i in range(10):
...     r = my_sum(10000000)
...
cnt 1, time 0.937***, 0.937***
cnt 2, time 1.852***, 0.914***
cnt 3, time 2.747***, 0.895***
cnt 4, time 3.681***, 0.934***
cnt 5, time 4.601***, 0.919***
cnt 6, time 5.563***, 0.962***
cnt 7, time 6.494***, 0.930***
cnt 8, time 7.403***, 0.909***
cnt 9, time 8.374***, 0.970***
cnt 10, time 9.280***, 0.906***
```

# 5. partial function

1. Syntax: functools Partial (function object, *args, **kwargs)
2. Parameters: function object, positional parameter, named parameter

The functools module of Python provides many functions, one of which is Partial Function;
Functools Partial function: help us create a partial function, but we do not need to define the partial function ourselves;

For example, to convert a binary string to a decimal integer, you can define a function int2

```def int2(x, base=2):
return int(x, base=base)
```

In this way, binary strings are processed by default

However, we do not need to define the function int2 ourselves. We can use partial functions to achieve the purpose:

```>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('00010000')
16
```

Functools Function of partial: fix some parameters of a function (that is, set default values) and return a new function;
However, when int2 is called, you can still specify the base parameter as another value:

```>>> int2('00010000'，base=10)
10000
```

Example 1:

```>>> int2 = functools.partial(int, base=2)
>>> int2('00010000')
>>>
>>> #Equivalent to:
>>> kwargs = {'base':2} # Use 'base': 2 as part of kwargs
>>> int('00010000', **kwargs)
```

Example 2:

```>>> max_with_10 = functools.partial(max, 10)
>>> max_with_10(5, 6, 7)
>>>
>>> #Equivalent to:
>>> args=(10, 5, 6, 7) # Automatically add 10 to the left as part of *args
>>> max(*args)
```

Posted by Dagwing on Tue, 31 May 2022 11:49:10 +0530