函数

1 函数基础

1.1 致命三问

  • 1、什么是函数
    函数就是盛放代码的容器,把实现某一功能的一组代码丢到一个函数中,就做成了一个小工具

具备某一功能的工具 ——–》函数
事先准备工具的过程 ——–》函数的定义
遇到应用场景拿来就用 ——-》函数的调用

  • 2、为何要用函数
    在没有用函数之前面临的问题:
    1、代码冗余,程序组织结构不清晰、可读性差
    2、扩展性差

  • 3、如何用函数
    原则:先定义、后调用

    • 定义函数的语法:
1
2
3
4
5
6
7
8
def 函数名(参数1,参数2,参数3,...):
"""
文档注释
"""
代码1
代码2
代码3
return 返回值
  • **如何调用函数:**函数名(1,2,3)
  • 先定义、后调用
    1
    2
    3
    4
    5
    6
    7
    def func():
    print("start...")
    print("xxx")
    print("stop...")
    func()
    func()
    func()

    1.2 函数在定义阶段发生的什么事情

    定义函数不执行函数体代码,但是会检测函数体语法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # x = 100
    # print(x)

    def func(): # func=函数的内存地址
    print("start...")
    x
    # if True
    # print('ok')
    print("xxx")
    print("stop...")
    # 这个时候不会报错因为语法是正确的

    func()
    # 执行函数的时候就会报错了,因为x没有定义

    1.3函数在调用阶段发生的什么事情

    先通过函数名找到函数的内存地址,然后函数的内存地址()会触发函数体代码的运行
    1
    2
    3
    4
    5
    6
    def func():  # func=函数的内存地址
    print("start...")
    print("xxx")
    print("stop...")

    func()

练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#案例1
def foo():
print('from foo')
bar()
foo()

# 报错,因为函数bar没有定义

# 案例2
def bar():
print('from bar')

def foo():
print('from foo')
bar()

foo()
#不会报错

#案例3
def foo():
print('from foo')
bar()

def bar():
print('from bar')

foo()
# 不会报错

2 函数参数

2.1 分类

  • 1、形参:在函数定义阶段括号内定义的变量名,称之为形式参数,简称形参
  • 2、实参:在函数调用阶段括号内传入的值,称之为实际参数,简称实参

    2.2 形参与实参的关系

    在调用函数时,实参的值会绑定给形参名,然后可以在函数内使用,函数调用完毕后,解除绑定
    1
    2
    3
    4
    5
    6
    7
     def func(x,y):
    x=1
    y=2
    print(x+y)
    m = 1
    n = 2
    func(m,n)

    2.3参数详解

    2.3.1 位置形参

    在函数定义阶段按照从左到右的顺序依次定义的形参,称之为位置形参
  • 特点:必须被传值,多一个不行少一个也不行
    1
    2
    3
    4
    5
    6
    def func(x,y):
    print(x+y)
    func(1,2)
    #下面两种方法都是错误的
    func(1,2,3)
    func(1,)

    2.3.2 默认形参

    在函数定义阶段就已经为某个形参赋值了,称之为默认形参
  • 特点: 在函数定义阶段就已经赋值了,意味着在调用阶段可以不用为其赋值

ps:可以混用位置形参与默认形参,但是位置形参必须在前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def func(x,y=2):
print(x)
print(y)

func(1,3)
func(1)

def register(name,age,gender="male"):
print(name,age,gender)

register("李铁蛋",38)
register("王全蛋",19)
register("赵银蛋",28)
register("lili",18,"female")

默认形参需要注意的问题是:

  • 1、默认形参的值只在函数定义阶段被赋值一次
  • 2、默认形参的值通常应该是不可变类型

    案例1

    1
    2
    3
    4
    5
    6
    7
    m = 100
    def func(x,y=m): # y -> 100的内存地址
    print(x,y)

    m = 200
    func(1)
    #执行结果为100

    案例2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    m = [11,22,]
    def func(x,y=m): # y -> [11,22,]的内存地址
    print(x,y)

    # m = 200
    m.append(33)
    func(11,m)

    # 结果11 [11, 22, 33]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def register(name,x,hobbies=None):  # hobbies = []内地址
    if hobbies is None:
    hobbies = []
    hobbies.append(x)
    print("%s 的爱好是 %s" %(name,hobbies))

    register("egon",'read')
    register("liu",'chou')
    register("hxx",'tang')

    2.3.3 位置实参

在函数调用阶段按照从左到右的顺序依次传入的值,称之为位置实参
特点:按照顺序与形参一一对应

1
2
3
4
5
6
def func(x,y):
print(x)
print(y)

func(1,2)
func(2,1)

2.3.4 关键字实参

在函数调用阶段按照key=value的格式传入的值,称之为关键字实参
特点:可以打乱顺序,但是仍然能够为指定的形参赋值
ps:可以混用位置实参与关键字实参,但是

  • 1、位置实参必须在关键字实参前
  • 2、不能为同一个形参重复赋值
    1
    2
    3
    4
    5
    6
    7
    8
    def func(x,y):
    print(x)
    print(y)

    func(y=2,x=1)
    func(1,y=2)
    func(y=2,1)
    func(1,2,y=3)

2.3.5 *与**在形参与实参中的应用

  • 可变长的参数:可变长指的是在函数调用阶段,实参的个数不固定,而实参是为形参赋值的,所以对应着必须要有一种特殊格式的形参能用来接收溢出的实参

  • 形参中带*:
    *会接收溢出的位置实参,然后将其存成元组,然后赋值给紧跟其后的变量名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def func(x,*args):  # y=(2, 3, 4, 5, 6)
    print(x)
    print(args)

    func(1,2,3,4,5,6)

    def my_sum(*args):
    res = 0
    for num in args:
    res += num
    print(res)

    my_sum(1,2,3,4,5)
    my_sum(1,2)
    1
    *后跟的必须是一个可以被for循环遍历的类型,*会将实参打散成位置实参
    1
    2
    3
    4
    5
    6
    def func(x,y,z):
    print(x)
    print(y)
    print(z)
    func(*[11,22,33]) # func(11,22,33)
    func(*"hello") # func("h","e","l","l","o")
  • 形参中带
    :**

    **会接收溢出的关键字实参,然后将其存成字典,然后赋值给紧跟其后的变量名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
      def func(x,**kwargs):  #y = {'a':1,'b':2,'c':3}
    print(x)
    print(kwargs)

    func(1,a=1,b=2,c=3)
    func(1,2,3,a=1,b=2,c=3) # 报错
    ````
    **后跟的必须是一个字典,会将实参打散成关键字实参
    ```python
    def func(x,y,z):
    print(x)
    print(y)
    print(z)

    func(**{"x":1,"y":2,"z":3}) # func(z=3,x=1,y=2)

    def index(x,y,z):
    print(x,y,z)

    def wrapper(*args,**kwargs): # args = (1,2,3,4,5,6,7) kwargs= {"a":1,"b":2,"c":3} 此处*和**的作用是聚合
    # print(args)
    # print(kwargs)
    index(*args,**kwargs) # index(*(1,2,3,4,5,6,7),**{"a":1,"b":2,"c":3}) 此处 * 和 **的作用是打散
    # index(1,2,3,4,5,6,7,a=1,b=2,c=3)

    # wrapper(1,2,3,4,5,6,7,a=1,b=2,c=3)
    # wrapper(1,2,3)
    wrapper(1,z=3,y=2)

    命名关键字参数、函数对象、函数嵌套…

2.3.6 命名关键字参数

命名关键字参数:
*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递,可以保证,传入的参数中一定包含某些关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def func(x,n=3,*args,m=333,y,**kwargs):
print(x)
print(n)
print(y)
print(m)
print(args)
print(kwargs)

func(1,2,3,4,y=4,m=5555,a=1,b=2)

结果:
1
2
4
555
(3,4)
{'a':1,'b':2}


def register(name:str,age:int)->str: #这里 :str 的作用是提示传入的参数需为字符串. :int 的作用是提示传入的参数需为int, --str 的作用是提示函数的返回值为 str
print(name,age)
return 'ok'

res = register(111111111,"aaaaa")

3 函数对象

函数对象:函数是第一类对象,函数是第一等公民
函数可以被当成变量去用

1
2
3
4
def func():  # func = 函数的内存地址
print('from func')

x = 10 # x = 10的内存地址
  • 1、可以被赋值
    1
    2
    3
    4
    y = x
    f = func
    print(f is func)
    f()
  • 2、可以当做参数传给另外一个函数
    1
    2
    3
    4
    5
    def foo(a):
    print(a)
    a()

    foo(func)
  • 3、可以当做函数的返回值
    1
    2
    3
    4
    5
    6
    def foo(f):
    return f

    res = foo(func)
    # print(res)
    res()
  • 4、可以当做容器类型的元素
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    l = [x,func]
    # print(l[0])
    print(l)
    l[1]()

    #示例
    def login():
    print('登录'.center(50,'*'))

    def register():
    print('注册'.center(50,'*'))

    def transfer():
    print('转账'.center(50,'*'))

    def withdraw():
    print('提现'.center(50,'*'))

    func_dic = {
    "1":["登录",login],
    "2":["注册",register],
    "3":["转账",transfer],
    "4":["提现",withdraw],
    }

    while True:
    print("0 退出")
    for k in func_dic:
    print(k,func_dic[k][0])

    choice = input("请输入您的命令编号:").strip()
    if choice == "0":
    break

    if choice in func_dic:
    func_dic[choice][1]()
    else:
    print("输入命令错误")

4 函数的嵌套

4.1 函数的嵌套定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def f1():
x = 111
def f2():
print('from f2')
# print(x)
# print(f2)
f2()

f1()

from math import pi

def circle(radius,mode=0):
def perimiter(radius):
return 2 * pi * radius

def area(radius):
return pi * (radius ** 2)

if mode == 0:
return perimiter(radius)
elif mode == 1:
return area(radius)

circle(3,1)

4.2 函数的嵌套调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def max2(x,y):
if x > y:
return x
else:
return y

def max4(a,b,c,d):
res1 = max2(a,b,)
res2 = max2(res1,c)
res3 = max2(res2,d)
return res3

res = max4(1,2,3,4)
print(res)

5 名称空间Namespaces

存放名字与其对应的内存地址的地方

5.1 名称空间分类

  • 1、内置名称空间:存放的是python解释器自带的名字。生命周期:解释器启动则产生,解释器关闭则销毁
    1
    2
    print(len)
    print(input)
  • 2、全局名称空间:存放的是顶级的名字。生命周期:py程序刚开始运行则立即产生,py程序结束
    1
    2
    3
    4
    5
    6
    x = 10
    if True:
    y = 2

    def foo():
    z = 3
  • 3、局部名称空间:存放的是函数内的名字。生命周期:函数调用则产生,函数调用结束则销毁
    1
    2
    3
    4
    5
    6
    7
    8
    len = 111

    def foo():
    len = 222
    # print(len)

    foo()
    print(len)

    5.2 LEGB

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__

    - locals 是函数内的名字空间,包括局部变量和形参

    - enclosing 外部嵌套函数的名字空间(闭包中常见)

    - globals 全局变量,函数定义所在模块的名字空间

    - builtins 内置模块的名字空间

    **总结:**

    - 1、名字的访问优先级:基于自己当前所在的位置向外一层一层查找,L-》E-》G-》B
    - 2、名称空间的”嵌套”关系是函数定义阶段、扫描语法时生成的,与调用位置无关
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    x = 0

    def f1():
    def f2():
    # x = 2222
    def f3():
    # x = 3333
    print(x)
    f3()
    x = 1111
    f2()

    f1()

    # # 案例1:
    x = 666
    def f1():
    print(x)
    x=111

    f1()

    # 案例2:
    x = 666

    def f1():
    x = 1111
    def f2():
    print('f2===>',x)
    return f2

    f = f1()

    def foo():
    x=77777777777777777
    f()

    foo()

    6 闭包函数

    6.1 闭包函数定义

闭函数:被封闭起来的函数=>定义函数内部的函数,闭函数的特点是只能在函数内用

1
2
3
4
def outter():
x = 100
def wrapper():
print(x)

包函数:该函数引用了一个名字,该名字是来自与e这一层的
总结:闭包函数指的是定义在函数内部的函数引用了一个来自于外层函数作用域中的名字

闭包函数升级:结合函数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def outter():
x = 100
def wrapper():
print(x)
return wrapper # 千万不要加括号

f = outter()

print(f)

def foo():
x = 666
f()

foo()

6.2 为函数体传参有两种解决方案

  • 方案一:直接以参数的形式传入
    1
    2
    3
    4
    def wrapper(x):
    print(x)

    wrapper(100)
  • 方案二:
    1
    2
    3
    4
    5
    6
    7
    8
    def outter(x):
    # x = 100
    def wrapper():
    print(x)
    return wrapper # 千万不要加括号

    f = outter(100)
    f()

    7 装饰器

    7.1 装饰器致命三问

    7.1.1 什么是装饰器

    装饰器指的是装饰别人的工具,装饰指的则是为被装饰者添加新功能

但是实现装饰器必须遵循的原则是”开放封闭原则”
开放指的是对拓展新功能是开放的,封闭指的则是对修改源码以及调用方式是封闭的

综上装饰器指的是我们要创建一个工具,该工具可以在遵循原则1和2的前提下还能为被装饰对象添加新功能

  • 原则1、不修改被装饰对象源代码
  • 原则2、不修改被装饰对象调用方式

7.1.2 为何要用装饰器

7.1.3 如何实现装饰器

装饰器-》函数
被装饰者-》函数
准备被装饰者

1
2
3
4
5
6
7
import time

def index():
print('from index')
time.sleep(3)

index()

7.2 装饰器的实现

方案一:改变被装饰者的源代码

1
2
3
4
5
6
7
8
9
10
import time

def index():
start = time.time()
print('from index')
time.sleep(1)
end = time.time()
print('run time is %s' %(end - start))

index()

方案二:装饰器的功能需要重复编写,代码冗余

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time

def index():
print('from index')
time.sleep(1)

start = time.time()
index()
end = time.time()
print('run time is %s' %(end - start))

start = time.time()
index()
end = time.time()
print('run time is %s' %(end - start))

方案三:解决方案二的代码冗余问题,但是存在问题-》装饰器的功能写死了,只能装饰index函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time

def index():
print('from index')
time.sleep(1)

def wrapper():
start = time.time()
index()
end = time.time()
print('run time is %s' %(end - start))

wrapper()
wrapper()

方案四:装饰器的功能写活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import time

def index():
print('from index')
time.sleep(1)

def outter(func):
# func = index
def wrapper():
start = time.time()
func()
end = time.time()
print('run time is %s' %(end - start))
return wrapper

index = outter(index) # index = wrapper的内存地址
# print(index)
index()

方案五:装饰器的功能写活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import time

def index():
print('from index')
time.sleep(1)

def home(name):
print('welcome %s to home page' % name)
time.sleep(0.5)
return 123

def outter(func):
# func = 最原始那个home的内存地址
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs) # 最原始那个home的内存地址()
end = time.time()
print('run time is %s' % (end - start))
return res
return wrapper


home = outter(home) # home = wrapper的内存地址
index = outter(index) # home = wrapper的内存地址

res = home('egon')
print(res)

# res = index()
# print(res)

方案六、装饰器正确写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import time

def outter(func):
# func = 最原始那个home的内存地址
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs) # 最原始那个home的内存地址()
end = time.time()
print('run time is %s' % (end - start))
return res
return wrapper

@outter # index = outter(index)
def index():
print('from index')
time.sleep(1)


@outter # home = outter(home)
def home(name):
print('welcome %s to home page' % name)
time.sleep(0.5)
return 123


res = home('egon')
print(res)

7.3 装饰器模板

1
2
3
4
5
def deco(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#统计时间的装饰器
import time

def timmer(func):
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print(stop - start)
return res
return wrapper

认证功能装饰器
def auth(func):
def wrapper(*args,**kwargs):
inp_user = input('Username: ').strip()
inp_pwd = input('Password: ').strip()
if inp_user == 'egon' and inp_pwd == '123':
res = func(*args,**kwargs)
return res
else:
print('登录失败')
return wrapper

@auth # index = auth(index)
def index():
print('from index')

index()

7.4 多装饰器调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def deco1(func1):  # func1 = wrapper2的内存地址
def wrapper1(*args,**kwargs):
print('===>wrapper1')
res1 = func1(*args,**kwargs)
print('=====>end wrapper1')
return res1
return wrapper1

def deco2(func2): # func2 = wrapper3的内存地址
def wrapper2(*args,**kwargs):
print('===>wrapper2')
res2 = func2(*args,**kwargs)
print('=====>end wrapper2')
return res2
return wrapper2

def deco3(func3): # func3 = 最原始那个index的内存地址
def wrapper3(*args,**kwargs):
print('===>wrapper3')
res3 = func3(*args,**kwargs)
print('=====>end wrapper3')
return res3
return wrapper3

# index = wrapper1的内存地址
@deco1 # deco1(wrapper2的内存地址) ---> wrapper1的内存地址
@deco2 # deco2(wrapper3的内存地址) ---> wrapper2的内存地址
@deco3 # deco3(最原始那个index的内存地址) ---> wrapper3的内存地址
def index():
print('=======>index')

index()

7.5 有参装饰器

需求:实现一个用来为被装饰对象添加认证功能的装饰器

1
2
3
4
5
#基本形式如下
def deco(func):
def wrapper(*args,**kwargs):
编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
return wrapper

如果我们想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下

1
2
3
4
5
6
7
def deco(func):
def wrapper(*args,**kwargs):
if mode == 'file':
编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
elif mode == 'mysql':
编写基于mysql认证,认证通过则执行res=func(*args,**kwargs),并返回res
return wrapper

函数wrapper需要一个mode参数,而函数deco与wrapper的参数都有其特定的功能,不能用来接受其他类别的参数,可以在deco的外部再包一层函数auth,用来专门接受额外的参数,这样便保证了在auth函数内无论多少层都可以引用到

1
2
3
4
def auth(mode):
def deco(func):
''''''
return deco

此时我们就实现了一个有参装饰器,使用方式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#先调用auth(mode='file'),得到@deco,deco是一个闭包函数,包含了对外部作用域名字mode的引用,@deco的语法意义与无参装饰器一样
@auth(mode='file')
def index():
pass
@auth(driver='mysql')
def home():
pass
#最终版
def auth(mode,yyy):
# mode = 'mysql'
def deco(func): # func = index
def wrapper(*args ,**kwargs):
print(yyy)
inp_user = input('Username: ').strip()
inp_pwd = input('Password: ').strip()
if mode == 'file':
if inp_user == "egon" and inp_pwd == "123":
res = func(*args ,**kwargs)
return res
else:
print('login failed')
elif mode == 'mysql':
print("基于mysql数据库的认证")
elif mode == 'ldap':
print("基于ldap数据库的认证")
else:
print('未知的认证来源')
return wrapper
return deco

@auth('mysql',111) # @outter # index = outter(index) # index = wrapper
def index():
print('from index')

@auth('ldap',222)
def home(name):
print('hello %s' %name)
# print(index)
# index()

home('egon')

7.6 wraps函数

可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@timer
def home(name):
'''
home page function
:param name: str
:return: None
'''
time.sleep(5)
print('Welcome to the home page',name)

print(help(home))
'''
打印结果:

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

None

在被装饰之后home=wrapper,查看home.name也可以发现home的函数名确实是wrapper,想要保留原函数的文档和函数名属性,需要修正装饰器

1
2
3
4
5
6
7
8
9
10
def timer(func):
def wrapper(*args,**kwargs):
start_time = time.time()
res = func(*args,*kwargs)
stop_time = time.time()
print('run time is %s' %(stop_time-start_time))
return res
wrapper.__doc__ = func.__doc__
wrapper.__name__ = func.__name__
return wrapper

按照上述方法实现保留原函数输赢过于麻烦,functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用发如下

1
2
3
4
5
6
7
8
9
10
11
from functools import wraps

def timer(func):
@wraps(func)
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper

7.7 总结

装饰器模板

  • 1、无参装饰器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def outter(func):
    def wrapper(*args,**kwargs):
    res = func(*args,**kwargs)
    return res
    return wrapper

    @outter
    def index():
    pass
  • 2、有参装饰器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def outter2(xxx):
    def outter(func):
    def wrapper(*args,**kwargs):
    xxx
    res = func(*args,**kwargs)
    return res
    return wrapper
    return outter

    @outter2(xxx=111)
    def index():
    pass

    8 迭代器

    8.1 迭代器致命三问

    8.1.1 什么是迭代器

  • 迭代器:迭代取值的工具。
  • 迭代:迭代是一个重复的过程,但是每次重复都是基于上一次的结果而来
    1
    2
    3
    4
    5
    6
    7
    names = ["egon","tom","lili"]

    def func(items):
    i = 0
    while i < len(items):
    print(items[i])
    i += 1

    8.1.2 为何要用迭代器

    对于序列类型:字符串、列表、元祖,我们可以使用索引的范式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引测,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器

    8.1.3 如何用迭代器

  • 可迭代的对象
    • 内置有__iter__方法
  • 迭代器对象
    • 内置有__iter__方法
    • 内置有__next__方法
      调用: 可迭代对象.__iter__() –> 返回迭代器对象
      调用:迭代器对象.__next__() –> 返回的是下一个值
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      #示例
      #字符串、列表、元祖、字典、集合、文件都是可迭代对象,都有__iter__方法
      "abc".__iter__()
      [1,2,3].__iter__()
      (1,2,3).__iter__()
      {'k1':111}.__iter__()
      {111,22}.__iter__()
      open('b.txt',mode='wb').__iter__()
      open('b.txt',mode='wb').__next__()


      dic = {'name':'egon','age':18,'gender':'male'}
      dic = "hello"
      iter_dic = dic.__iter__()
      print(iter_dic)
      print(iter_dic.__iter__().__iter__().__iter__() is iter_dic) # 返回结果是True,对迭代器对象执行__iter__()方法,返回的结果是它本身

      print(iter_dic)
      print(iter_dic.__next__()) #等同于next(iter_dic)
      print(iter_dic.__next__())
      print(iter_dic.__next__())
      print(iter_dic.__next__()) # StopIteration
      有了迭代器,我们就可以不依赖索引迭代取值了
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      dic = {'name':'egon','age':18,'gender':'male'}
      len("hello") # "hello".__len__()

      iter_dic = iter(dic) # dic.__iter__()

      while True:
      try:
      print(next(iter_dic))
      except StopIteration:
      break
      #这么写太丑陋了,需要我们自己捕捉异常、控制next,python这么牛逼,能不能帮我们解决呢?能,请看for循环

8.2 for循环

1
2
3
4
5
6
7
8
9
#基于for循环,我们可以完全不再依赖索引去取值了
dic = {'name':'egon','age':18,'gender':'male'}
for k in dic:
print(dic[k])

#for循环的工作原理
#1:执行in后对象的dic.__iter__()方法,得到一个迭代器对象iter_dic
#2: 执行next(iter_dic),将得到的值赋值给k,然后执行循环体代码
#3: 重复过程2,直到捕捉到异常StopIteration,结束循环

注意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dic = {'name':'egon','age':18,'gender':'male'}
iter_dic = iter(dic)
for k in iter_dic:
print(k)
print('='*50)
for k in iter_dic:
print(k)
#这里第二次打印的结果为空,为什么会这样呢?
#iter_dic 是一个迭代器对象,用for就相当于执行了next(iter_dic),将得到的值赋值给了k,第一次循环结束迭代器对象的值已经被取完。所以第二次for循环是取不到值的

#直接对字典for循环就不一样了
#每次for循环都相当于重新生成了一个迭代器对象,所以两次都能打印出所有值
for k in dic:
print(k)
print('='*50)
for k in dic:
print(k)

8.3 总结

迭代器对象优缺点:

  • 优点:
  1. 提供了一种新的、统一的取值方式(可以不依赖于索引以及key的)
  2. 惰性计算,不耗费内存
  • 缺点:
  1. 取值不够灵活
  2. 一次性的,只能往后取,无法预知数据的个数

    9 生成器

    9.1 生成器走起

    函数内但凡出现yield关键字,再调用函数不会执行函数体代码,会得到一个生成器对象。生成器就是一种自定义的迭代器。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #示例
    def func():
    print('hello1')
    yield 1
    print('hello2')
    yield 2
    print('hello3')
    yield 3
    print('hello4')

    g = func()
    # print(g)
    # g.__iter__()
    # g.__next__()
    #g也有__iter__、__next__方法,所以生成器可以这么取值
    res1 = next(g)
    print(res1)

    res2 = next(g)
    print(res2)

    res3 = next(g)
    print(res3)

    next(g)

实现一个可从1开始可以取到无穷大的生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def func():
start = 0
while True:
yield start
start += 1

res = func()
print(res)
print(next(res))
print(next(res))
print(next(res))
#自定义函数实现range功能
def my_range(start,stop,step=1):
while start < stop:
yield start
start += step

for i in my_range(1,7,2): # 1 3 5
print(i)

yiled VS return:

  • 相同点:返回值层面都一样

  • 不同点:return只能返回一次值函数就立即结束,而yield可以返回多次值

    9.2 列表生成式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    nums = []
    for i in range(1,6):
    nums.append(i**2)

    nums = [i**2 for i in range(1,6)]
    nums = ['ok' for i in range(1,6)]
    print(nums)

    names = ['egon_nb',"lxx_sb","hxx_sb"]

    new_l = []
    for name in names:
    if name.endswith("_sb"):
    new_l.append(name)

    new_l = [name for name in names if name.endswith("_sb")]
    print(new_l)

    9.3 字典生成式

    1
    2
    3
    4
    5
    res = {i:i**2 for i in range(5)}
    print(res,type(res))

    items = [('k1',111),('k2',222)]
    print({k:v for k,v in items})

    9.4 集合生成式

    1
    2
    res = {i for i in range(5)}
    print(res,type(res))

    9.5 生成器表达式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    res = (i for i in range(5))
    print(res,type(res))

    print(next(res))
    print(next(res))
    print(next(res))
    print(next(res))
    print(next(res))
    print(next(res))
  • 统计文本文件a.txt中的字符串个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
with open('a.txt',mode='rt',encoding='utf-8') as f:
data = f.read()
print(len(data)) # 24
#很明显,用这种方式统计,当文本文件很大的时候,执行f.read()时候,服务器的内存一下子就被占满了,太浪费内存空间了

#利用for循环一行一行地读,内存占用就会小很多
res = 0
for line in f:
res += len(line)

#利用生成器表达式实现
lines_size = (len(line) for line in f)
res = sum(lines_size)

res = sum(len(line) for line in f) #这里的)可以省略
print(res)

面向对象

面向过程编程是一种编写思想or编程范式

面向过程核心是“过程”二字,过程就是流程,流程指的就是先干什么、再干什么、后干什么

基于面向过程编写程序就好比在设计一条条流水线

  • 面向过程的优缺点
    优点:复杂的问题流程化、进而简单化
    缺点:牵一发而动全身,扩展性差

函数递归调用、模块、匿名函数

10 函数的递归调用

10.1 定义

函数的递归调用是函数嵌套调用的一种特殊形式。
具体指的是在调用一个函数的过程中又直接或者间接地调用自己,称之为函数的递归调用
函数的递归调用其实就是用函数实现的循环

1
2
3
4
5
6
#一下就是一个递归的例子
def f1():
print('from f1')
f1()

f1()

10.2 递归深度

1
2
3
4
5
6
import sys
print(sys.getrecursionlimit())
sys.setrecursionlimit(2000)
#1. 可以使用sys.getrecursionlimit()去查看递归深度,默认值为1000,虽然可以使用
# sys.setrecursionlimit()去设定该值,但仍受限于主机操作系统栈大小的限制
#2. python不是一门函数式编程语言,无法对递归进行尾递归优化

一个递归的过程应该分为两个阶段:

  • 1、回溯:向下一层一层调用
  • 2、递推:向上一层一层返回

    10.3 应用

  • 1、推算年龄
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ## 分析过程:
    age(5) = age(4) + 10
    age(4) = age(3) + 10
    age(3) = age(2) + 10
    age(2) = age(1) + 10
    age(1) = 18

    n > 1 -> age(n) = age(n-1) + 10
    n = 1 -> age(1) = 18

    ##实现
    def age(n):
    if n == 1:
    return 18
    return age(n-1) + 10

    res = age(5)
    print(res)
  • **2、打印出一个列表中的所有元素
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    nums = [1,[2,[3,[4,[5,[6,[7,[8,]]]]]]]]

    def func(nums):
    for item in nums:
    if type(item) is list:
    func(item)
    else:
    print(item)

    # func([1,[2,[3,[4,[5,[6,[7,[8,]]]]]]]])
  • **3、利用二分法从nums中找出需要导入的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
nums = [-3,1,5,7,9,11,13,18,22,38,78,98]

def search(find_num,nums):
print(nums)
if len(nums) == 0:
print('not exists')
return
mid_index = len(nums) // 2
if find_num > nums[mid_index]:
# in the right
new_nums = nums[mid_index+1:]
search(find_num,new_nums)
elif find_num < nums[mid_index]:
# in the left
new_nums = nums[:mid_index]
search(find_num,new_nums)
else:
print('find it')

# search(22,nums)
search(23,nums)

11 匿名函数

11.1 lambda

对比使用def关键字创建的是有名字的函数,使用lambda关键字创建则是没有名字的函数,即匿名函数,语法如下:
lambda 参数1,参数2,...: expression
匿名函数与有名函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放,所以匿名函数用于临时使用一次的场景,匿名函数通常与其他函数配合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
##例1
res = (lambda x,y:x + y)(1,2)
print(res)

##例2
salaries = {
'axx':1000000,
'zxx':3000,
'egon':4000,
}

# print(max([1,2,3,4]))

# def func(k):
# return salaries[k]

# print(max(salaries,key=func))
# print(max(salaries,key=lambda k:salaries[k]))
# print(min(salaries,key=lambda k:salaries[k]))
# print(sorted(salaries,key=lambda k:salaries[k]))
# print(sorted(salaries,))

11.2 filter、map、reduce

map、reduce、filter都支持迭代器协议,用来处理可迭代对象,我们以一个可迭代对象array为例来介绍它们三个的用法
array=[1,2,3,4,5]
#要求一:对array的每个元素做平方处理,可以使用map函数map函数可以接收两个参数,一个是函数,另外一个是可迭代对象,具体用法如下

11.2.1 map

1
2
3
4
5
6
7
>>> res=map(lambda x:x**2,array)
>>> res
<map object at 0x1033f45f8>
>>>
#解析:map会依次迭代array,得到的值依次传给匿名函数(也可以是有名函数),而map函数得到的结果仍然是迭代器。
>>> list(res) #使用list可以依次迭代res,取得的值作为列表元素
[1, 4, 9, 16, 25]

11.2.2 reduce

要求二:对array进行合并操作,比如求和运算,这就用到了reduce函数

1
2
3
4
5
6
7
8
#reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值# reduce在python2中是内置函数,在python3中则被集成到模块functools中,需要导入才能使用
>>> from functools import reduce
>>> res=reduce(lambda x,y:x+y,array)
>>> res
#解析:1 没有初始值,reduce函数会先迭代一次array得到的值作为初始值,作为第一个值数传给x,然后继续迭代一次array得到的值作为第二个值传给y,运算的结果为3
# 2 将上一次reduce运算的结果作为第一个值传给x,然后迭代一次array得到的结果作为第二个值传给y,依次类推,知道迭代完array的所有元素,得到最终的结果15也可以为reduce指定初始值
>>> res=reduce(lambda x,y:x+y,array,100)>>> res
115

11.2.3 filter

要求三:对array进行过滤操作,这就用到了filter函数,比如过滤出大于3的元素

1
2
3
4
>>> res=filter(lambda x:x>3,array)
#解析:filter函数会依次迭代array,得到的值依次传给匿名函数,如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器。
>>> list(res)
[4, 5]