# 认识函数

## 什么是函数

函数是组织好的，可重复使用的，用来实现单一，或相关联功能的代码段。

## 函数的定义与调用

```python
def my_len():
    s = 'hello world'
    length = 0
    for i in s:
        length = length + 1
    print(length)

my_len()
```

```
定义：def 关键词开头，空格之后接函数名称和圆括号()，最后还有一个":"。

　　　def 是固定的，不能变，他就是定义函数的关键字。

　　　空格 为了将def关键字和函数名分开，必须空(四声)，当然你可以空2格、3格或者你想空多少都行，但正常人还是空1格。

　　　函数名：函数名只能包含字符串、下划线和数字且不能以数字开头。虽然函数名可以随便起，但我们给函数起名字还是要尽量简短，并能表达函数功能

　　　括号：是必须加的，先别问为啥要有括号，总之加上括号就对了！

注释：每一个函数都应该对功能和参数进行相应的说明，应该写在函数下面第一行。以增强代码的可读性。

调用：就是 函数名() 要记得加上括号。
```

## 函数的返回值

```python
def my_len():
    s = 'hello world'
    length = 0
    for i in s:
        length = length + 1
    return length

str_len = my_len()
print(str_len)
```

**return关键字的作用**

- return 是一个关键字，这个词翻译过来就是“返回”，所以我们管写在return后面的值叫“返回值”。
- 不写return的情况下，会默认返回一个None
- 一旦遇到return，结束整个函数。
- 返回的多个值会被组织成元组被返回，也可以用多个值来接收

```python
def ret_demo():
    return 1,2,'a',['hello','world']

ret = ret_demo()
print(ret)
```

## 函数的参数

带参数的函数

```python
def my_len(s):
    length = 0
    for i in s:
        length += 1
    return length

ret = my_len('hello world!')
print(ret)
```

实际的要交给函数的内容，简称实参。

在定义函数的时候它只是一个形式，表示这里有一个参数，简称形参。

1. 按照位置传值：位置参数

```python
def maxnumber(x,y):
    the_max = x if x > y else y
    return the_max

ret = maxnumber(10,20)
print(ret)
```

1. 按照关键字传值：关键字参数。

```python
def maxnumber(x,y):
    the_max = x if x > y else y
    return the_max

ret = maxnumber(y = 10,x = 20)
print(ret)
```

1. 位置、关键字形式混着用：混合传参。

```python
def maxnumber(x,y):
    the_max = x if x > y else y
    return the_max

ret = maxnumber(10,y = 20)
print(ret)
```

位置参数必须在关键字参数的前面

对于一个形参只能赋值一次

1. 默认参数。

```python
def stu_info(name,age = 18):
    print(name,age)

stu_info('aaron')
stu_info('song',50)
```

1. 默认参数是一个可变数据类型

```python
def demo(a,l = []):
    l.append(a)
    print(l)

demo('abc')
demo('123')
```

1. 动态参数

```python
def demo(*args,**kwargs):
    print(args,type(args))
    print(kwargs,type(kwargs))

demo('aaron',1,3,[1,3,2,2],{'a':123,'b':321},country='china',b=1)

##动态参数，也叫不定长传参，就是你需要传给函数的参数很多，不定个数，那这种情况下，你就用*args，**kwargs接收，args是元祖形式，接收除去键值对以外的所有参数，kwargs接收的只是键值对的参数，并保存在字典中。
```

## 命名空间和作用域

代码在运行伊始，创建的存储“变量名与值的关系”的空间叫做全局命名空间；

在函数的运行中开辟的临时的空间叫做局部命名空间。

命名空间一共分为三种：

- 全局命名空间
- 局部命名空间
- 内置命名空间

取值顺序：

- 在局部调用：局部命名空间->全局命名空间->内置命名空间
- 在全局调用：全局命名空间->内置命名空间

作用域

- 全局作用域：包含内置名称空间、全局名称空间，在整个文件的任意位置都能被引用、全局有效
- 局部作用域：局部名称空间，只能在局部范围内生效

### globals和locals方法

```python
print(globals())
print(locals())

def func():
    a = 12
    b = 20
    print(globals())
    print(locals())
    
func()
```

**global**

1. 声明一个全局变量。
2. 在局部作用域想要对全局作用域的全局变量进行修改时，需要用到 global(限于字符串，数字)。

```python
def func():
    global a
    a = 3

func()
print(a)

count = 1
def search():
    global count
    count = 2

search()
print(count)
```

对可变数据类型（list，dict，set）可以直接引用不用通过global

```python
li = [1,2,3]
dic = {'name':'aaron'}

def change():
    li.append(4)
    dic['age'] = 18
    print(dic)
    print(li)

change()
print(dic)
print(li)
```

**nonlocal**

1. 不能修改全局变量。
2. 在局部作用域中，对父级作用域（或者更外层作用域非全局作用域）的变量进行引用和修改，并且引用的哪层，从那层及以下此变量全部发生改变。

```python
def add_b():
    b = 1
    def do_global():
        b = 10
        print(b)
        def dd_nolocal():
            nonlocal b  # 应用了上一层的变量b
            b = b + 20
            print(b)    # 发生了改变
        dd_nolocal()    # 调用函数，导致do_global的命名空间b也改变了
        print(b)
    do_global()
    print(b)
add_b()     # 最上面一层没有变化
```

## 函数的嵌套和作用域链

```python
def mymax(x,y):
    m = x if x > y else y
    return m

def maxx(a,b,c,d):
    res1 = mymax(a,b)
    res2 = mymax(res1,c)
    res3 = mymax(res2,d)
    return res3

ret = maxx(23,453,12,-13)
print(ret)

# 传入任意多的数字，甚至有重复的，还有非数字(忽略)，得到最大的值

def my_max(x, y):
    m = x if x > y else y
    return m


def maxx(*nums):
    temp_nums = []
    for i in nums:
        if type(i) is int:
            temp_nums.append(i)
    while len(temp_nums) > 1:
        a_num = temp_nums.pop()
        b_num = temp_nums.pop()
        ret = my_max(a_num, b_num)
        temp_nums.append(ret)
    return temp_nums[0]


ret = maxx(123, 3345, 123, 9999, 123, 345, 345, 667, 123, 435, 657, 16, 21, "aabc", ["a", "b"])
print(f"数值类型中最大的是{ret}")
```

```python
def f1():
    print("in f1")
    def f2():
        print("in f2")
    f2()
    
f1()
```

## 函数名的本质

函数名本质上就是函数的内存地址

1. 可以被引用

```python
def func():
    print('in func')

f = func

print(f)
f()
```

1. 可以被当作容器类型的元素

```python
def f1():
    print('f1')

def f2():
    print('f2')

def f3():
    print('f3')

l = [f1,f2,f3]
d = {'f1':f1,'f2':f2,'f3':f3}

#调用
l[0]()
d['f2']()
```

1. 可以当作函数的参数和返回值

```python
def f1():
    print('f1')

def func(argv):
    argv()
    return argv

f = func(f1)
f()
```

## 闭包

```python
def func():
    name = 'aaron'
    def inner():
        print(name)
    return inner

f = func()
f()
```

内部函数包含对外部作用域而非全剧作用域变量的引用，该内部函数称为闭包函数

判断闭包函数的方法**closure**

```python
def func():
    name = 'aaron'
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

f = func()
f()
# 最后运行的结果里面有cell就是闭包

name = 'aaron'
def func():
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

f = func()
f()
# 输出结果为None，说明不是闭包
```

```python
def wrapper():
    money = 1000
    def func():
        name = 'apple'
        def inner():
            print(name,money)
        return inner
    return func

f = wrapper()
i = f()
i()
```

```python
def func(a,b):
    def inner(x):
        return a*x + b
    return inner

func1 = func(4,5)
func2 = func(7,8)
print(func1(5),func2(6))
```

```python
from urllib.request import urlopen
def func():
    content = urlopen('http://myip.ipip.net').read()
    def get_content():
        return content
    return get_content

code = func()
content = code()
print(content.decode('utf-8'))

content2 = code()
print(content2.decode('utf-8'))
```