函数进阶,装饰器,生成器,迭代器
名称空间
定义:假如有 x = 1 , 1是存放到内存的,那 x 存哪里的呢?名称空间是存放名字x与1绑定关系的地方。
名称空间分3种:
- locals:是函数内的名称空间,包括局部变量和形参。通过locals()可以打印当前名称空间的局部变量,如果在函数里就是函数里的局部变量。
- globals:全局变量,函数定义所在模块的名字空间。通过globals()可以打印所有的全局变量,无论是在函数内还是函数外。
- builtins:内置模块的名字空间。可通过dir(__builtins__)打印所有的内置方法
不同变量的作用域不同就是由这个变量所在的命名空间来决定的。
作用域即范围:
- 全局范围:全局存活,全局有效
- 局部范围:临时存活,局部有效。
作用域查找顺序:
LEGB代表名字查找顺序:locals --> enclosing function --> globals --> __builtins__
- locals:是函数内的名字空间
- enclosing:外部嵌套函数的名字空间
- globals:全局变量,函数定义所在模块的名字空间
- builtins:内置模块的名字空间
闭包
首先看例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def func(): name = "Alex" def inner(): print("在inner里打印name:", name) return inner f = func() f()
输出:
在inner里打印name: Alex
以上例子可以看出,嵌套函数中,直接执行func函数,里面的inner函数并没有执行,而是func函数将inner函数对象返回给了外部,给到了f ,这时候 f 实际上就是inner函数,f 加上括号就相当于直接执行inner函数。这样使得嵌套在函数内的子函数在外部也可被调用执行,调用的时候依然能够获取inner函数所在作用域的变量以及参数。
关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含他们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必须访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。
闭包的意义:
返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得该函数无论在何处调用,优先使用自己外层包裹的作用域。
定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).
闭包详解引用:
https://www.cnblogs.com/JohnABC/p/4076855.html
装饰器
装饰器就是在不修改原来程序的情况下给程序添加新的功能。
- 软件开发中的一个原则:开放-封闭原则
- 开放:已实现的功能代码块不应该被修改
- 封闭:对现有功能的扩展开放
装饰器练习题
1 ''' 2 一:编写3个函数,每个函数执行的时间是不一样的, 3 提示:可以使用time.sleep(2),让程序sleep 2s或更多, 4 5 二:编写装饰器,为每个函数加上统计运行时间的功能 6 提示:在函数开始执行时加上start=time.time()就可纪录当前执行的时间戳,函数执行结束后在time.time() - start就可以拿到执行所用时间 7 8 三:编写装饰器,为函数加上认证的功能,即要求认证成功后才能执行函数 9 10 四:编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码 11 提示:从文件中读出字符串形式的字典,可以用eval('{"name":"egon","password":"123"}')转成字典格式 12 ''' 13 14 import time 15 16 17 def runtime(func): 18 def inner(): 19 start_time = time.time() 20 func() 21 end_time = time.time() 22 run_time = end_time - start_time 23 print("run time:", run_time) 24 25 return inner 26 27 28 def get_account_from_file(): 29 f = open("account2.txt", mode="r", encoding="utf-8") 30 data = eval(f.read()) 31 f.close() 32 return data 33 34 35 login_status = False 36 37 38 def login(func): 39 def inner(*args, **kwargs): 40 global login_status 41 if login_status == False: 42 username = input("username:>") 43 password = input("password:>") 44 account_dict = get_account_from_file() 45 if username == account_dict["name"] and password == account_dict["password"]: 46 print("登录成功") 47 login_status = True 48 else: 49 print("用户名或密码错误.") 50 51 if login_status: 52 print("已通过验证..") 53 func(*args, **kwargs) 54 55 return inner 56 57 58 @login 59 @runtime 60 def func1(): 61 print("func1 running...") 62 time.sleep(2) 63 64 65 @login 66 @runtime 67 def func2(): 68 print("func2 running...") 69 time.sleep(3) 70 71 72 @login 73 @runtime 74 def func3(): 75 print("func3 running...") 76 time.sleep(1) 77 78 79 func1() 80 func2() 81 func3()
列表生成式
有一个需求:有一个列表,要对列表里的每个元素加1,如何做,有以下几种
# 版本一: a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] b = [] for i in a: b.append(i + 1) print(b) # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 版本二: c = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] for index, i in enumerate(c): c[index] = i + 1 print(c) # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 版本三: d = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] d = list(map(lambda x: x + 1, d)) print(d) # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 版本四:列表生成式 e = [i + 1 for i in range(10)] print(e) # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
f = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] f = [i if i < 5 else i * i for i in f] # 列表生成式 print(f) # 输出:[0, 1, 2, 3, 4, 25, 36, 49, 64, 81]
生成器
通过前面的列表生成式,我们可以很容易的创建一个列表,但是假如我要创建100万的数据,那么这100万的数据就会全部在内存中,而内存是有限的,不可能让你无限制的存,而且我只需要访问前面的一部分数据,后面的其实根本不会使用,还以这样的方式创建就对资源消耗太大了。
如果列表里的元素可以按照某种算法推算出来,那我们就可以循环的推算出后面的元素的值,就可以不用完整的创建整个列表从而大大节省资源消耗,这种一边循环一边计算的机制,就称为生成器(generator)
要创建一个generator,有很多种方法,最简单的只要把列表生成式的 [ ] 改为 ( ) ,就创建了一个generator:
>>> L=[i*i for i in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> >>> >>> g=(i*i for i in range(10)) >>> g <generator object <genexpr> at 0x0000022BD464E150> >>>
如何获取generator里的元素呢?通过next()方法来获取。
说明:
生成器生成的只是方法,并不执行。
next()只能往前走,不能往回退。意思就是已经通过next()获取的元素不能够再次获取。
generator保存的是算法,每次调用next(g)就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素的时候,抛出StopIteration异常。
一步一步的nexg(g)很麻烦,我们可以通过for循环来获取元素值,还不会报StopIteration异常
g = (i * i for i in range(10)) for j in g: print(j)
如果瑞算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如斐波那契数列(Fibonacci),除第一个和第二个数外,任意一个数都是由前两个数相加得到,用列表生成式写不出来,但是用函数打印出来很容易:
1 def fib(max): 2 n, a, b = 0, 0, 1 3 while n < max: 4 print(b) 5 a, b = b, a + b 6 n = n + 1 7 return 'done' 8 9 fib(10)
注意:
a, b = b, a + b
相当于:
t = a + b
a = b
b = t
说到这里我们还没有用到生成器,回头想一下生成器的实现逻辑,再来看看这个斐波那契数列的程序,fib函数实际上是定义了斐波那契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这个逻辑和生成器的逻辑是非常相似的。
上面的函数里生成器 generator就是一步之遥,要把fib函数变成generator,只需要把print(b)n 改为 yield b 就可以了:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def fib(max): 2 n, a, b = 0, 0, 1 3 while n < max: 4 # print(b) 5 yield b # 改为 yield 就变成了generator,遇到yield程序就停在这里,等到下一次next()时,程序继续从这里开始运行,然后又等待下一次next(),循环往复,知道报出StopIteration异常 6 a, b = b, a + b 7 n = n + 1 8 return 'done' 9 10 11 g = fib(10) 12 print(g) # 输出 <generator object fib at 0x000001F14DE340F8> 说明函数变成了generator 13 print(next(g)) # next(g) 出generator里的元素 14 print(next(g)) 15 print(next(g)) 16 print(next(g)) 17 print(next(g)) 18 print("其他程序在这个地方执行一下") # 在next的过程中依然可以执行其他的程序 19 print(next(g)) # 还可以继续执行之前的next 20 print(next(g)) 21 print(next(g)) 22 print(next(g)) 23 print(next(g))
输出结果
<generator object fib at 0x000001F14DE340F8> 1 1 2 3 5 其他程序在这个地方执行一下 8 13 21 34 55
关于执行流程:
函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。
变成generator的函数,在每次调用 next() 的时候执行,遇到yield语句返回,再次被next()调用时,从上次返回的yield语句处继续执行。
yield后面加上a,就表示返回a,类似于 return a ,但yield不会退出,return会退出。
关于如何拿到generator里的return返回值:
如果我们再加一个next(g) 这时候就要报StopIteration异常了,因为已经没有值可以拿了,但是函数最后有一个 return "done" 的返回值,我们是可以看到的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 Traceback (most recent call last): 2 <generator object fib at 0x000001D4FA4540F8> 3 File "D:/PycharmProjects/python_fullstack_middle/第二模块:函数编程/第1章·函数、装饰器、迭代器、内置方法/函数/生成器.py", line 41, in <module> 4 1 5 1 6 2 7 3 8 5 9 其他程序在这个地方执行一下 10 8 11 13 12 21 13 34 14 55 15 print(next(g)) 16 StopIteration: done
如果使用 for 循环则拿不到generator里面的 return 返回值。如果要拿到返回值,必须要捕获StopIteration异常,返回值在StopIteration的value中。
g = fib(10) while True: try: x = next(g) print("g:", x) except StopIteration as e: print("Genrator return value:", e.value) break
输出
g: 1 g: 1 g: 2 g: 3 g: 5 g: 8 g: 13 g: 21 g: 34 g: 55 Genrator return value: done
关于捕获异常,后面还会详细讲解。
再来看看生成器的调用方法:
g = (i for i in range(10)) print(next(g)) print(next(g)) print(next(g)) print("---------------") for i in g: print(i)
输出
0 1 2 --------------- 3 4 5 6 7 8 9
next()之后,之前的值就没有了,所以看到for循环的时候是从3开始的
for循环其实就是每循环一次就next一次
range其实也是一个生成器
关于range:
在python2里:
range(100000000000000) 会直接生成这么多个数据
在python3里:
range(100000000000000) 直接生成一个公式,根本就没有创建,在python2里也有同样的 不过叫 xrange(100000000000000) 和py3的range是一样的。
python2:
range = list
xrange = 生成器
python3
range = 生成器
xrange 没有
函数写生成器:
生成器的创建方式:
1. 列表生成式
2. 函数
区别:
列表生成式最后只能写一个三元运算,更复杂的情况没办法办到。
所以要用到函数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def range2(n): 2 count = 0 3 while count < n: 4 print("count", count) 5 count += 1 6 yield count 7 8 9 new_range = range2(10) 10 11 r1 = next(new_range) 12 print(r1) 13 r2 = next(new_range) 14 print(r2) 15 r3 = next(new_range) 16 print(r3)
输出:
count 0 # 由print打印的 1 # 由yield返回的 count 1 2 count 2 3
yield vs return:
return:返回并终止function
yield:返回数据,并冻结当前的执行过程。
next 唤醒冻结的函数执行过程,继续执行,知道遇到下一个yield
生成器send方法:
在函数里已经有了yield后,再写return是不会返回return值的,并且会报StopIteration异常。
函数有了yield之后:
1. 调用时函数名加 () 就便得到了一个生成器
2. return 在生成器里,代表生成器的中止,直接报错。
send作用:
1. 唤醒并继续执行
2. 发送一个信息到生成器内部
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def range2(n): 2 count = 0 3 while count < n: 4 print("count", count) 5 count += 1 6 sign = yield count 7 print("收到来自send的消息::", sign) 8 if sign == "stop": 9 print("我要停止程序运行了,bye bye") 10 break # break后就执行return了,return后会报错,需要自己捕获 11 return 333 12 13 14 new_range = range2(3) 15 n1 = next(new_range) 16 new_range.send("stop")
输出:
count 0 Traceback (most recent call last): 收到来自send的消息:: stop 我要停止程序运行了,bye bye File "D:/PycharmProjects/python_fullstack_middle/第二模块:函数编程/第1章·函数、装饰器、迭代器、内置方法/函数/生成器send.py", line 16, in <module> new_range.send("stop") StopIteration: 333
send()里面不写值的话,默认是发送一个None的。
next()默认也是发了一个None的。
next(iterator, default=None) 如果后面的参数给了值的话,但next完所有元素的时候也不会报StopIteration异常。
通过yield实现在单线程的情况下实现并发运算的效果:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import time 2 3 4 def consumer(name): 5 print("%s 准备吃包子啦!" % name) 6 while True: 7 baozi = yield 8 print("包子[%s]来了,被[%s]吃了!" % (baozi, name)) 9 10 11 def producer(name): 12 c = consumer('A') 13 c2 = consumer('B') 14 c.__next__() 15 c2.__next__() 16 print("老子开始准备做包子啦!") 17 for i in range(10): 18 time.sleep(1) 19 print("做了%s个包子!" % (i)) 20 c.send(i) 21 c2.send(i) 22 23 24 producer("alex")
输出:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 A 准备吃包子啦! 2 B 准备吃包子啦! 3 老子开始准备做包子啦! 4 做了0个包子! 5 包子[0]来了,被[A]吃了! 6 包子[0]来了,被[B]吃了! 7 做了1个包子! 8 包子[1]来了,被[A]吃了! 9 包子[1]来了,被[B]吃了! 10 做了2个包子! 11 包子[2]来了,被[A]吃了! 12 包子[2]来了,被[B]吃了! 13 做了3个包子! 14 包子[3]来了,被[A]吃了! 15 包子[3]来了,被[B]吃了! 16 做了4个包子! 17 包子[4]来了,被[A]吃了! 18 包子[4]来了,被[B]吃了! 19 做了5个包子! 20 包子[5]来了,被[A]吃了! 21 包子[5]来了,被[B]吃了! 22 做了6个包子! 23 包子[6]来了,被[A]吃了! 24 包子[6]来了,被[B]吃了! 25 做了7个包子! 26 包子[7]来了,被[A]吃了! 27 包子[7]来了,被[B]吃了! 28 做了8个包子! 29 包子[8]来了,被[A]吃了! 30 包子[8]来了,被[B]吃了! 31 做了9个包子! 32 包子[9]来了,被[A]吃了! 33 包子[9]来了,被[B]吃了!
迭代器
可以理解为 迭代器=循环 ,迭代一次,循环一次。
可以直接作用于 for 循环的数据类型有以下几种:
- 一类是集合数据类型,如 list、tuple、dict、set、str等;
- 一类是generator,包括生成器和带 yield 的generator function
这些可以直接作用于 for 循环的对象统称为可迭代对象:Iterable
使用 isinstance() 判断一个对象是否是 Iterable对象:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 >>> from collections import Iterable 2 >>> isinstance([],Iterable) 3 True 4 >>> isinstance({},Iterable) 5 True 6 >>> isinstance("abc",Iterable) 7 True 8 >>> isinstance((x for x in range(10)),Iterable) 9 True 10 >>> isinstance(100,Iterable) 11 False 12 >>>
生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,知道最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Itetator
同样可以使用 isinstance()判断一个对象是否是 Iterator对象:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 >>> from collections import Iterator 2 >>> isinstance((x for x in range(10)),Iterator) 3 True 4 >>> isinstance([],Iterator) 5 False 6 >>> isinstance({},Iterator) 7 False 8 >>> isinstance("abc",Iterator) 9 False 10 >>>
生成器都是 Itrator 对象。
list 、dict、str 是 Iterable,但不是 Iterator。
可以把 list、dict、str 等 Iterable 变成 Iterator ,使用 iter() 函数:
>>> li = [1,2,3,4,5,7] >>> isinstance(li,Iterable) True >>> isinstance(li,Iterator) False >>> li2=iter(li) >>> isinstance(li2,Iterator) True
为什么 list、dict、str 等数据类型不是 Iterator ?
因为Python的 Iterator对象表示的是一个数据流,Iterator对象可以被 next() 函数调用并不断返回下一个数据,知道没有数据时抛出 Stopitration 异常。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过 next() 函数实现按需计算下一个数据,所以 Iterator的计算是惰性的,只有在需要返回下一个数据时他才会计算。
Iterator 甚至可以表示一个无限大的数据流,例如全体自然数。而 list 是永远不可能存储全体自然数的。
小结:
凡是可作用于 for 循环的对象都是 Iterable 类型;
凡是可作用于 next() 函数的对象都是 Iterator类型,他们表示一个惰性计算的序列;
集合数据类型如 list、dict、str等时Iterable但不是Iterator,不过可以通过iter() 函数变成 Itrator对象。
Python3的for循环本质上就是通过不断调用 next()函数实现的,如:
for x in [1, 2, 3, 4, 5] pass
完全等价于:
it = iter([1, 2, 3, 4, 5]) # 首先获得Itrator对象 while True: try: x = next(it) # 获取下一个值 except StopIteration: break # 遇到StopIteration就退出循环