迭代器
一.迭代器了解
1.迭代器:
(1)特殊的对象,必定是可迭代对象,具有可迭代对象的特征
(2)生成器对象,就是迭代器对象
2.迭代器协议:
(1)迭代:每次循环获得一个结果,依赖于上次结果来的叫迭代
(2)协议:协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代协议访问对象
(3)经过iter方法把一个可迭代对象封装成迭代器,对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引发一个Stoplteration异常,以终止迭代(只能日后不能往前退)
3.可迭代对象:遵循了迭代器协议的对象
(1)可以经过迭代一次次返回不一样的元素的对象。所谓相同不是指值是否相同,而是元素在容器中是不是同一个,列如列表中值能够重复
(2)能够迭代,可是未必有序,未必可索引
(3)可迭代对象有:list,tuple,string,bytes,bytearray,range,set,dict,生成器等
(4)可使用成员操做符in,not in,in本质上就是在遍历对象
二.python中for循环机制
1.for循环的本质
循环全部对象,全都是使用迭代协议,跟索引没有一点关系(字符串,列表,元祖,字典,集合,文件对象)这些都不是可迭代对象,只不过在for循环式,调用了他们内部的__iter__(迭代)方法,把他们变成可迭代对象而后for循环调用可迭代对象的__next__方法取值,并且for循环会捕捉Stoplteration异常,以终止迭代
2.for循环的本质举例:python
x='wang' iter_test=x.__iter__() print(iter_test) #iter_test是字符串迭代器对象 print(iter_test.__next__()) #__next__方法取值:输出w print(iter_test.__next__()) #__next__方法取值:输出a print(iter_test.__next__()) #__next__方法取值:输出n print(iter_test.__next__()) #__next__方法取值:输出g #print(iter_test.__next__()) #超出会报错StopIteration
输出:
<str_iterator object at 0x00000000006BAAC8>
w
a
n
g
三.迭代器的访问方式
1.索引访问方式程序员
l=[1,2,3] print(l[0]) print(l[1]) print(l[2]) #print(l[3]) 超出边界报错:IndexError 输出: 1 2 3
2.遵循迭代器协议访问方式算法
l=[1,2,3] iter_l=l.__iter__() #遵循迭代器协议,生成可迭代对象赋值给iter print(iter_l.__next__()) #经过next取值 print(iter_l.__next__()) print(iter_l.__next__()) #print(iter_l.__next__()) 超出边界报错:StopIteration 输出: 1 2 3
3.for循环访问方式:
for循环是基于迭代器协议提供了一个统一的能够遍历全部对象的方法,即在遍历以前,先调用对象的__iter__方法将其转换成一个迭代器,而后使用迭代器协议去实现循环访问,这样全部的对象就均可以经过for循环来遍历
(1)for循环访问列表:缓存
l=[1,2,3] for i in l: print(i) 输出: 1 2 3
详解:for循环l本质就是遵循迭代器协议的方式,先调用diedai_l=1.__iter__()方法,或者直接diedai_l=iter(1),而后依次执行diedai_l.next(),直到for循环捕捉到StopIteration终止循环,for循环全部对象的本质都是同样的原理
(2)for循环访问集合:(集合是无序的)数据结构
s={1,2,3} #for i in s: # print(i) iter_s=s.__iter__() #经过s下面的iter方法把他作成可迭代对象放在上面 print(iter_s) print(iter_s.__next__()) #以此执行next方法取值 print(iter_s.__next__()) print(iter_s.__next__()) #print(iter_s.__next__()) 超出边界报错:StopIteration 输出: <set_iterator object at 0x01E1F2D8> 1 2 3
(3)for循环访问字典闭包
dic={'a':1,'b':2} iter_d=dic.__iter__() print(iter_d.__next__()) 输出: a
详解:字典在取值去的是字典的key的值,由于for循环调用的字典变成iter_d可迭代对象的next方法,这个next获得的方法就是key值
(4)for循环访问文件
文件内容:xixi.txt
hello
123app
f=open('xixi.txt','r+') #for i in f: # print(i) iter_f=f.__iter__() print(iter_f) print(iter_f.__next__(),end='') print(iter_f.__next__(),end='') 输出: <_io.TextIOWrapper name='xixi.txt' mode='r+' encoding='cp936'> hello 123
4.用while循环去模拟for循环:(while循环没有下标不能遍历字典,集合,文件)dom
l=[1,2,3,4,5] diedai_l=l.__iter__() while True: try: print(diedai_l.__next__()) except StopIteration: print('迭代完毕了,循环终止了') break 输出: 1 2 3 4 5 迭代完毕了,循环终止了
详解:for循环本质作了两件事:1.diedai_l=l.__iter__(),2.except StopIteration:捕捉异常
生成器
能够理解为一种数据类型,这种数据类型自动实现了迭代协议(其余的数据类型须要调用本身的内置的__iter__方法),因此生成器就是可迭代对象,生成器自动实现了迭代器协议
生成器在python的表现形式有两种:
第一种:生成器表达式形式
相似于列表推导,可是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
一.了解三元表达式
1.举例:函数
name='xixi' name='wangxixi' res='恐龙' if name == 'xixi' else '帅哥' #把三元表达式赋值给res print(res) 输出: 帅哥
详解:
if判断若是是true返回'恐龙', else若是是False返回'帅哥'
第1元-判断条件:if name == 'xixi'
第2元-判断条件若是是true获得的返回值:'恐龙'
第3元-判断结果若是是false返回的值:else '帅哥'
二.列表解析List Comprehension
1.列表解析语法:
(1)[返回值for元素in可迭代对象if条件]
(2)使用中括号[],内部是for循环,if条件语句可选
(3)返回一个新的列表
(4)举例:
<1>正常写法返回新列表:工具
xixi = [ ] for x in range(10): if x % 2 ==0: xixi.append(x) print(xixi) 返回: [0, 2, 4, 6, 8]
<2>经过列表解析式写法:
xixi = [x for x in range(10) if x%2==0] print(xixi) 返回: [0, 2, 4, 6, 8]
2.列表解析式的优势:
(1)是一种语法糖,编译器会优化,不会由于简写而影响效率,反而因优化提升了效率
(2)减小程序员工做量,减小出错
(3)简化了代码,但可读性加强
3.三元表达式列表解析方式
(1)单层循环:
[expr for item in iterable if cond1 if cond2] 等价于:
ret=[] for item in iterable: if cond1: if cond2: ret.append(expr)
举例:20之内,既能被2整除又能被3整除的数
xx = [i for i in range(20) if i%2==0 and i%3==0] print(xx) 返回: [0, 6, 12, 18]
详解三元:
第1元:for i in range(20)
第2元:i
第3元:if i%2==0 and i%3==0
(2)多层循环:
[expr for i in iterable1 for j in iterable2] 等价于:
ret = [] for i in iterable1: for j in iterable2: ret.append(expr)
举例:
xx = [(x,y) for x in 'abcde' for y in range(3) ] print(xx) 返回: [('a', 0), ('a', 1), ('a', 2), ('b', 0), ('b', 1), ('b', 2), ('c', 0), ('c', 1), ('c', 2), ('d', 0), ('d', 1), ('d', 2), ('e', 0), ('e', 1), ('e', 2)]
4.练习
(1)返回1-10平方的列表
print([i**2 for i in range(1,11)]) 返回结果: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
(2)有一个列表lst=[1,4,9,16,2,5,10,15]生成一个新列表,要求新列表元素是lst相邻2项的和
lst=[1,4,9,16,2,5,10,15] print([lst[i] + lst[i+1] for i in range(len(lst)-1)]) 返回结果: [5, 13, 25, 18, 7, 15, 25]
(3)打印九九乘法表
print('\n'.join([''.join(['%s*%s=%-3s' % (x,y,y*x) for x in range(1,y+1)]) for y in range(1,10)])) #一行一行算好迭代9次 返回结果: 1*1=1 1*2=2 2*2=4 1*3=3 2*3=6 3*3=9 1*4=4 2*4=8 3*4=12 4*4=16 1*5=5 2*5=10 3*5=15 4*5=20 5*5=25 1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36 1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49 1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64 1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
(4)"0001.abadicddws"是ID格式,要求ID格式是以点号分割,左边是4位从1开始的证书,右边是10位随机小写英文字母,请依次生成前10个ID的列表
import random print(["{:04}.{}".format(i,"".join([chr(random.randint(97,122)) for j in range(10)])) for i in range(1,11)]) 返回: ['0001.vqtxhottae', '0002.retetyzcnl', '0003.ztiicqfjxo', '0004.uxoyywqven', '0005.qwstljbqet', '0006.mgqvuhhqrq', '0007.sauzsxpozx', '0008.ytgvceidnr', '0009.slrkbyewfe', '0010.kksmodmgfh']
三.生成器表达式:把列表解析的[]换成()获得的就是生成器表达式,生成器不用iter方法本身就有next(省内存)
1.语法:(返回值 for 元素 in 可迭代对象 if 条件)返回一个生成器
laomuji=('鸡蛋%s' %i for i in range(10)) #生成器表达式把列表解析赋值给laomuji print(laomuji) #获得生成器:<generator object <genexpr> at 0x01E6E900> print(laomuji.__next__()) print(laomuji.__next__()) print(next(laomuji)) print(next(laomuji)) print(next(laomuji)) print(next(laomuji)) print(next(laomuji)) print(next(laomuji)) print(next(laomuji)) print(next(laomuji)) #print(next(laomuji)) #超出边界报错:StopIteration 输出: <generator object <genexpr> at 0x01E6E900> 鸡蛋0 鸡蛋1 鸡蛋2 鸡蛋3 鸡蛋4 鸡蛋5 鸡蛋6 鸡蛋7 鸡蛋8 鸡蛋9
2.生成器表达式特色:生成器表达式是按需机选(或称惰性求值,延迟计算),须要的时候才计算值
3.生成器是可迭代对象同时也是迭代器(可迭代对象不必定是迭代器,迭代器必定是可迭代对象)
4.生成器表达式和列表解析式的对比
(1)生成器表达式:
g = ("{:04}".format(i) for i in range(1,5)) #返回一个生成器 next(g) #延迟计算 for x in g: print(x) print('~~~~~~~~~') for x in g: #不执行 print(x) 输出: 0002 0003 0004
~~~~~~~~
总结:
计算方式:延迟计算
内存占用:返回生成器(迭代器),能够迭代。生成器没有数据,内存占用极少,可是用的时候,虽然一个个返回数据,可是合起来占用内存也差很少
计算速度:生成器表达式耗时很是短,可是生成器自己并无返回任何值,只返回了一个生成器对象
从前到后走完一遍,不能回头
(2)对比列表解析式:
g = ["{:04}".format(i) for i in range(1,5)] #返回可迭代对象列表 for x in g: print(x) print('~~~~~~~~~') for x in g: #能够从新回头新迭代 print(x) 输出: 0001 0002 0003 0004 ~~~~~~~~~ 0001 0002 0003 0004
总结:
计算方式:当即计算
内存占用:返回的不是迭代器,返回可迭代对象列表。列表解析式构形成新的列表须要占用内存
计算速度:列表解析耗时长,构造并返回一个新的列表
从前到后走完一遍后,能够从新回头迭代
第二种:生成器函数(generator)
一.生成器
是生成器对象,能够由生成器表达式获得,也可使用yield关键字获得一个生成器函数,调用这个函数获得一个生成器对象
二.生成器函数
1.使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每一个结果中间,挂起函数的状态,以便下次它离开的地方继续执行。函数当中只要有yield,那么这个函数一执行,他获得的就是生成器,这个生成器本质就是数据类型,这个数据类型自动实现迭代器协议
(1)函数体中包含yield语句的函数,返回生成器对象
(2)生成器对象,是一个可迭代对象,是一个迭代器
(3)生成器对象,是延迟计算,惰性求值的
三.生成器函数举例
1.单个yield
def inc(): for i in range(5): yield i print(type(inc)) #第一次调用inc()函数,打印函数对象,yield返回值0 print(type(inc())) #第二次调用inc()函数,打印函数返回值的类型(生成器对象),yield返回值1 x = inc() #第三次调用inc()函数把生成器对象赋值给x print((type(x))) #调用inc()函数,打印函数返回值的类型(生成器对象) print(next(x)) #next第一次调用inc()函数,返回yield返回值0 for m in x: print(m, '*') #第一次迭代返回1* 2* 3* 4* for m in x: print(m,'**') #第二次迭代不执行 返回: <class 'function'> <class 'generator'> <class 'generator'> 0 1 * 2 * 3 * 4 *
总结:
1)普通的函数调用fn(),函数会当即执行完毕,可是生成器函数可使用next函数屡次执行
2)生成器函数等价于生成器表达式,只不过生成器函数能够更加的复杂
2.能够用来保存函数的状态
import time def test(): print('开始生孩子啦。。。。。。') yield '我' #至关于return time.sleep(3) print('开始生儿子啦') yield '儿子' time.sleep(3) print('开始生孙子啦') yield '孙子' res=test() print(res) print(res.__next__()) #test() print(res.__next__()) #test() print(res.__next__()) #test() 输出: <generator object test at 0x01DEE990> 开始生孩子啦。。。。。。 我 开始生儿子啦 儿子 开始生孙子啦 孙子
详解:yield至关于return帮你作返回值,还能够保留函数的运行状态,一遇到yield就停住了返回一个值在运行下一次next,从上一次next终止地方开始接着日后运行,而不是从头开始运行
3.能够用来保存函数的状态
def product_baozi(): for i in range(100): #建立100个包子 print('正在生产包子') yield '一个包子%s' %i print('开始卖包子') pro_g=product_baozi() #生产包子的生成器赋值给pro_g baozi1=pro_g.__next__() #当前值是1 baozi1=pro_g.__next__() #当前值是2 输出: 正在生产包子 开始卖包子 正在生产包子
详解:进行第一个baozi1=pro_g.__next__()运行开始执行for i in range(100):打印print('正在生产包子'),遇到yield '一个包子%s' %i返回一个值给baozi1=pro_g.__next__(),此刻函数停留到yield '一个包子%s' %i这个位置。再执行第二个baozi1=pro_g.__next__()从yield '一屉包子%s' %i这个位置位置开始运行,执行print('开始卖包子'),执行下一次for循环
4.yield的另一个特性,接受send传过来的值赋值
(1)send效果1:至关于一个next保证函数会继续运行
(2)send效果2:send的参数会传给yield转给赋的值
def test(): print('xixi1') xixi3=yield print('xixi2') yield 2 print(xixi3) yield 3 t=test() res=t.__next__() print(res) res=t.send('赋值xixi3') print(res) res=t.__next__()
详解:
xixi3=yield至关于接受send传过来的值赋值给xixi3
yield 2至关于return控制的是函数的返回值2
执行过程:
运行res=t.__next__()执行print('xixi1')打印xixi1,函数当前位置停在xixi3=yield的位置
运行print(res)打印xixi3=yield返回值为None
运行res=t.send('赋值给xixi3')执行print('xixi2')打印xixi2,函数当前位置停在yield 2
运行print(res)打印xixi2=yield返回值为2
运行res=t.__next__()执行print('xixi3')打印“赋值xixi3”,函数当前位置停在yield 3
举例:生产鸡蛋来一我的取走一个鸡蛋
def xiadan(): for i in range(100): yield '鸡蛋%s' %i laomuji=xiadan() jidan0=laomuji.__next__() #要了第一个鸡蛋0 jidan1=laomuji.__next__() #要了第二个鸡蛋1 print('xixi取走了',jidan0) #第一我的取走了鸡蛋0 print('shishi取走了',jidan1) #第二个取走了鸡蛋1 #输出: xixi取走了 鸡蛋0 shishi取走了 鸡蛋1
举例:
def f(): print("ok") #第二步:打印ok s=yield 6 #第三步:把6返回给RET 第九步:s接收5 print(s) #第十步:打印s print("ok2") #第十一步:打印ok2 yield gen=f() #加上yield后f()就是生成生成器对象(里面的代码不会被执行),赋值给gen #生成器对象能够作不断的程序切换 #方式一:next方法 RET=gen.__next__() #第一步:调用next 第四步:gen调用者接收返回值,RET接收返回值6 print(RET) #第七步:打印6 #方式二:send方法 gen.send(5) #第八步:send一个5 打印结果: ok 6 5 ok2
5.多个yield和return语句结合
def gen(): print('line 1') yield 1 print('line 2') yield 2 print('line 3') return 3 next(gen()) #调用gen()函数拿到生成器函数对象打印line 1碰到yield语句暂停 next(gen()) #调用gen()函数拿到生成器函数对象打印line 1碰到yield语句暂停 g = gen() #调用gen()函数把生成器对象赋值给g print(next(g)) #next第一次调用gen()函数找到第一个yield拿到生成器函数对象打印line 1,打印返回值1 print(next(g)) #next第二次调用gen()函数找到第二个yield拿到生成器函数对象打印line 2,打印返回值2 #print(next(g)) #next第三次调用gen()函数找不到第三个yield报错 print(next(g,'End')) #若是加上缺省值经过next第三次调用gen()函数就不会报错打印line 3,打印返回值是缺省值End print(next(g,'End')) #加上缺省值经过next第四次调用gen()函数,只会打印缺省值End,不会打印line 3,由于函数没有了 返回: line 1 line 1 line 1 1 line 2 2 line 3 End End
总结:
1)在生成器函数中,使用多个yield语句,执行一次后会暂停执行,把yield表达式的值返回
2)再次执行会执行到下一个yield语句
3)return语句依然能够终止函数运行,但return语句的返回值不能被获取到
4)return会致使没法继续获取下一个值,抛出StopIteration异常
5)若是函数 没有显示return语句,若是生成器函数执行到结尾,同样会抛出StopIteration异常
四.触发生成器的另外一种方式send方法:
send效果1:至关于一个next保证函数会继续运行
send效果2:send的参数会传给yield转给赋的值
def test(): print('开始了') firt=yield 1 #yield 1至关于return 1 print('第一次',firt) yield 2 print('第二次') t=test() res=t.__next__() #运行t.__next__()执行函数test(),从开始位置执行遇到yield中止,yield后面跟的值至关于return的返回值赋值给res print(res) #光标停留在firt=yield 1后面 打印结果:开始了 1 t.send(None) #send(None)至关于从firt=yield 1日后执行把None这个值给了yield,yield给了firt 打印结果:开始了 None 打印结果: 开始了 1 第一次 None
五.yield from
(1)yield from是python3.3出现的新的语法
(2)yield from iterable是for item in iterable:yield item形式的语法糖
从可迭代对象中一个个拿元素
def counter(n): #counter生成器函数返回可迭代对象给inc函数用 for x in range(n): #第三步:迭代第一次当前yield为0 yield x def inc(n): yield from counter(n) #第二步:调用counter()生成器对象函数把参数10传进去 foo = inc(10) #第一步:调用inc()函数把生成器对象赋值给foo传参数10进去 print(next(foo)) print(next(foo)) 返回: 0 1
六.生成器的应用
1.无线循环
def counter(): i = 0 while True: i += 1 yield i def inc(c): return next(c) c = counter() #第一步:调用counter()函数把生成器对象赋值给c print(inc(c)) #第二步:调用inc()函数把生成器对象扔进去return遇到next调用counter函数执行死循环里的0 += 1,当前yield=1返回1 print(inc(c)) #第三步:调用inc()函数把生成器对象扔进去return遇到next调用counter函数执行死循环里的1 += 1,当前yield=2返回2 返回: 1 2
2.计数器
(1)匿名函数写法:
def inc(): def counter(): #counter()是生成器函数 i = 0 while True: i += 1 yield i c = counter() #调用counter()函数把生成器对象赋值给c,c对于下面lambda : next(c)是自由变量 return lambda : next(c) #return返回的是匿名函数,在匿名函数里调用counter()函数把生成器对象,把函数对象返回,函数里面用到外面 c = counter()自由变量 foo = inc() #调用inc()函数返回值是lambda : next(c)匿名函数的引用赋值给foo,没有yield语句不是生成器对象 print(foo()) #第一次调用匿名函数lambda : next(c),next调用到counter()函数把生成器对象里的0 += 1返回yield 1 print(foo()) #第二次调用匿名函数lambda : next(c),next调用到counter()函数把生成器对象里的1 += 1返回yield 2 返回: 1 2
(2)非匿名函数写法:
def inc(): def counter(): #counter()是生成器函数 i = 0 while True: i += 1 yield i c = counter() #调用counter()函数把生成器对象赋值给c #_inc()函数等同于上面的return lambda : next(c) def _inc(): return next(c) return _inc foo = inc() #调用 _inc()函数,没有yield语句不是生成器对象 print(foo()) print(foo()) 返回: 1 2
3.处理递归问题
def fib(): x = 0 y = 1 while True: yield y x,y = y,x+y foo = fib() for _ in range(5): print(next(foo)) for _ in range(10): next(foo) print(next(foo)) 返回: 1 1 2 3 5 987
七.生成器总结
语法上和函数相似:生成器函数和常规函数几乎同样的。它们都是使用def语句进行定义,差异在于,生成器使用yield语句返回一个值,能够保存状态,而常规函数使用return语句只能返回一个值
状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便以后从它离开的地方继续执行
1.包含yield语句的生成器函数生成生成器对象的时候,生成器函数的函数体不会当即执行
2.next(generator)会从函数的当前位置向后执行到以后碰到的第一个yield语句,会弹出值,并暂停函数执行
3.再次调用next函数,和上一条同样的处理过程
4.没有多余的yield语句能被执行,继续调用next函数会抛出StopIteration异常
八.生成器优势:生成器只能遍历一次
优势1:生成器的好处是延迟计算,一次返回一个结果。也就是说,它不会一次生成全部的结果,这对于大多数巨量处理,很是有用
优势2:生成器还能有效提升代码可读性
九.利用生成器模拟生产者消费者模型
import time def consumer(name): #定义函数消费者--B程序 print('我是[%s],我准备开始吃包子了' %name) while True: #定义一个死循环true baozi=yield #实现等包子的过程把,yield能够保存函数的运行状态把yield赋值给baozi time.sleep(1) print('%s 很开心的把【%s】吃掉了' %(name,baozi)) #实现吃包子的过程传人名name和send传给yield的baozi def producer(): #定义函数生产者--A程序 c1=consumer('第一我的') #第一个吃货c1--这个函数是生成器函数赋值c1拿到生成器 c2=consumer('第二我的') #第二个吃货c2 c1.__next__() #运行c1--经过next执行 c2.__next__() #运行c2 for i in range(5): #作出5个包子 time.sleep(1) c1.send('香菜包子 %s' %i) #send传一个香菜包子 c2.send('韭菜包子 %s' %i) producer() #运行producer函数产生吃货 #输出结果: 我是[第一我的],我准备开始吃包子了 我是[第二我的],我准备开始吃包子了 第一我的 很开心的把【香菜包子 0】吃掉了 第二我的 很开心的把【韭菜包子 0】吃掉了 第一我的 很开心的把【香菜包子 1】吃掉了 第二我的 很开心的把【韭菜包子 1】吃掉了 第一我的 很开心的把【香菜包子 2】吃掉了 第二我的 很开心的把【韭菜包子 2】吃掉了 第一我的 很开心的把【香菜包子 3】吃掉了 第二我的 很开心的把【韭菜包子 3】吃掉了 第一我的 很开心的把【香菜包子 4】吃掉了 第二我的 很开心的把【韭菜包子 4】吃掉了
函数式装饰器
1.函数装饰器
装饰:为其余函数添加附加功能
器:本质就是函数
装饰器:本质上是一个Python函数,它可让其余函数在不须要作任何代码变更的前提下增长额外功能,装饰器的返回值也是一个函数对象。它常常用于有切面需求的场景,好比:插入日志、监控、性能测试、参数检查、事务处理、缓存、权限校、验路由等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,咱们就能够抽离出大量与函数功能自己无关的雷同代码并继续重用。归纳的讲,装饰器的做用就是为已经存在的对象添加额外的功能。
2.装饰器的俩个原则:
(1)不修改被修饰函数的源代码
(2)不修改被修饰函数的调用方式
3.无参函数式装饰器的实现:
函数装饰器的知识储备=高阶函数+函数嵌套+闭包
4.一步步实现函数式装饰器
#加法函数: def add(x,y): return x + y print(add(5,6)) #返回:11
需求:想加强它的功能,可以输出被调用过以及调用的参数信息
方式一:侵入式代码
def add(x,y): print("{},{}+{}".format(add.__name__,x,y)) #输出被调用过以及调用的参数信息 return x + y print(add(5,6)) #返回: add,5+6 11
总结:上面的函数是完成了需求,可是有如下的缺点
(1)打印语句的耦合过高,跟加法函数混在一块儿
(2)加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不应放在业务函数加法中
方式二:高阶函数方式实现
不想用侵入式代码把一些非业务的代码侵入式的加入非业务函数中
def add(x,y): #原函数 return x + y def logger(fn): x = fn(5,6) #参数被写死 print("{},{}+{}".format(fn.__name__,5,6)) return x print(logger(add)) #返回: add,5+6 11
总结:上面代码作到了业务功能分离,可是fn函数调用参数被写死是个问题
方式三:高阶函数+传参方式实现
用python参数中的可变参数类型,接收可变参数解决传参的问题,进一步改变
def add(x,y): #原函数 return x + y def logger(fn,*args,**kwargs): #接受可变参数:fn接收add函数,args接收参数 ret = fn(*args,**kwargs) #参数结构:args分解获得5,6传到add函数里 print("{},{}".format(fn.__name__, args)) return ret #把结果返回回去 print(logger(add,5,6)) #返回: add,(5, 6) 11
方式四:高阶函数+传参方式实现+柯里化嵌套函数+闭包方式
def add(x,y): #原函数 return x + y def logger(fn): #第二步:把add函数传到fn里,标识符fn在嵌套函数的内层函数中被用到,外面fn这个变量是自由变量 #嵌套函数 def _logger(*args,**kwargs): #第五步:传参:*args接收到5和6 print("{},{}".format(fn.__name__, args)) #fn变量保留下来,有了闭包就能用到形参fn,fn虽然是形参,但它也是外层函数的本地变量,对内层函数讲是自由变量,被引用到,造成闭包add函数不会消亡 ret = fn(*args,**kwargs) #第六步:解构:经过参数执行add函数获得赋值给ret return ret #第七步:把结果返回回去 return _logger #第三步:自由变量fn在内层函数被引用到,内层函数的引用返回出去造成闭包不丢掉 #柯里化写法: print(logger(add)(5,6)) #第一步:调logger传add放到到fn 第四步:当传5和6的时候 #返回: add,(5, 6) 11
方式五:高阶函数+传参方式实现+嵌套函数+闭包方式
def add(x,y): #原函数1 return x + y def logger(fn): #第二步:把add函数传到fn里,标识符fn在嵌套函数的内层函数中被用到,外面fn这个变量是自由变量 #嵌套函数 def _logger(*args,**kwargs): #第五步:传参:*args接收到5和6 print("{},{}".format(fn.__name__, args)) #fn变量保留下来,有了闭包就能用到形参fn,fn虽然是形参,但它也是外层函数的本地变量,对内层函数讲是自由变量,被引用到,造成闭包add函数不会消亡 ret = fn(*args,**kwargs) #第六步:解构:经过参数执行add函数获得赋值给ret return ret #第七步:把结果返回回去 return _logger #第三步:自由变量fn在内层函数被引用到,内层函数的引用返回出去造成闭包不丢掉 add = logger(add) #第一步:调logger传add函数放到到fn print(add(5,6)) #第四步:当传5和6的时候被_logger函数的*args收集 #返回: add,(5, 6) 11
方式六:装饰器写法
#装饰器函数 def logger(fn): #传入的add函数是装饰器函数的形参 def _logger(*args,**kwargs): print("{},{}".format(fn.__name__, args)) ret = fn(*args,**kwargs) return ret return _logger #返回值也是一个函数_logger @logger #等价于add(新add指向内层函数_logger) = logger(add) def add(x,y): #见到装饰器找下面的函数把函数名做为参数传给了logger return x + y print(add(5,6)) #返回: add,(5, 6) 11
总结:装饰器是高阶函数,但装饰器是对传入函数的功能的功能的装饰(功能加强)
无参装饰器总结:
(1)它是一个函数
(2)函数做为它的形参
(3)返回值也是一个函数
(4)可使用@functionname方式,简化调用
5.有参函数装饰器实现
需求:获取函数的执行时长,对时长超过阈值的函数记录一下
#无参函数式装饰器: import datetime import time def logger(fn): #传入的add函数是装饰器函数的形参 def wrap(*args,**kwargs): #before功能加强(显示调用的函数) print("args={},kwargs={}".format(args,kwargs)) start = datetime.datetime.now() ret = fn(*args,**kwargs) #after功能加强(判断函数执行时间) duration = datetime.datetime.now() - start print("调用的函数:{} 用时:{}s.".format(fn.__name__,duration.total_seconds())) return ret return wrap #返回值也是一个函数wrap @logger #等价于add(新add指向内层函数wrap) = wrap(add) def add(x,y): #见到装饰器找下面的函数把函数名做为参数传给了wrap time.sleep(2) return x + y print(add(4,7)) #返回 args=(4, 7),kwargs={} 调用的函数:add 用时:2.000114s. 11
经过装饰器有参数方式改造函数
import datetime import time def logger(t): #接收有参装饰器的参数 def _logger(fn): #wrap函数解决无参装饰器 def wrap(*args,**kwargs): #传入的add函数是装饰器函数的形参 #before功能加强(显示调用的函数) print("args={},kwargs={}".format(args,kwargs)) start = datetime.datetime.now() ret = fn(*args,**kwargs) #after功能加强(判断函数执行时间) duration = (datetime.datetime.now() - start).total_seconds() if duration > t: #判断执行时间大于t的打印出来 print("调用的函数:{} 用时:{}s.".format(fn.__name__,duration)) return ret #对原函数值的返回 return wrap #返回值也是一个函数wrap return _logger @logger(3) #等价于add(新add指向内层函数wrap) = logger(3)(add) def add(x,y): #见到装饰器找下面的函数把函数名做为参数传给了wrap time.sleep(5) return x + y print(add(4,7)) #返回: args=(4, 7),kwargs={} 调用的函数:add 用时:5.000286s. 11
经过装饰器有参数方式改造函数两个参数:
import datetime import time def logger(t1,t2): #经过柯里化接收有参装饰器的参数 def _logger(fn): #wrap函数解决无参装饰器 def wrap(*args,**kwargs): #传入的add函数是装饰器函数的形参 #before功能加强(显示调用的函数) print("args={},kwargs={}".format(args,kwargs)) start = datetime.datetime.now() ret = fn(*args,**kwargs) #after功能加强(判断函数执行时间) duration = (datetime.datetime.now() - start).total_seconds() if duration > t1 and duration < t2: #判断执行时间大于t1且小于t2的打印出来 print("调用的函数:{} 用时:{}s.".format(fn.__name__,duration)) return ret return wrap #返回值也是一个函数wrap return _logger @logger(3,5) #等价于add(新add指向内层函数wrap) = logger(3)(add) def add(x,y): #见到装饰器找下面的函数把函数名做为参数传给了wrap time.sleep(5) return x + y print(add(4,7)) #返回: args=(4, 7),kwargs={} 11
带参装饰器总结:
(1)它是一个函数
(2)函数做为它的形参
(3)返回值是一个不带参的装饰器函数
(4)使用@functionname(参数列表)方式调用
(5)能够看作在装饰器外层有加了一层函数
装饰器的应用
1.实现一个cache装饰器,实现可过时被清除的功能
简化设计,函数的形参定义不包含可变位置参数,可变关键词参数和keyword-only参数能够不考虑缓存满了以后的换出问题
(1)数据类型的选择:
缓存的应用场景,是有数据须要频繁查询,且每次查询都须要大量计算或者等待时间以后才能返回结果的状况,使用缓存来提升查询速度,用空间换取时间。
(2)cache应该选用什么数据结构
便于查询的,且能快速好到数据的数据结构
每次查询的时候,只要输入一致,就应该获得一样的结果(顺序也一致,列如减法函数,参数顺序不一致,结果不同)
基于上面的分析,此数据结构应该是字典。
经过一个key,对应一个value
key是参数列表组成的结构,value是函数返回值。难点在于key如何处理
(3)key的存储
key必须是hash类。
key能接受到位置参数和关键字参数传参
位置参数是被收集在一个tuple中,自己就有顺序。
关键字参数收集在一个字典中,自己无序,这会带来一个问题,传参的顺序未必是字典中保存的顺序
OrderedDict能够,它能够记录顺序。不用OrderedDict能够,用一个tuple保存拍过序的字典的item的kv对。
(4)key的异同
(5)key的要求
key必须是hashable
因为key是全部实参组合而成,并且最好要做为key,key必定要能够hash,可是若是key有不可hash类型数据,就没法完成。
(6)key算法设计
inspect模块获取函数签名后,取parameters,这是一个有序字典,会保存全部参数的信息
构建一个字典params_dict,按照位置顺序从args中依次对应参数名和传入的实参,组成kv对,存入params_dict中
keargs全部值update到params_dict中
若是使用了缺省值的参数,不会出如今实参params_dict中,会出如今签名的取parameters中,缺省值也在定义中。
(7)调用的方式
普通的函数调用能够,可是过于明显,最好相似lru_cache的方式,让调用者无察觉的使用缓存。
(8)过时功能:通常缓存系统都有过时功能。
过时什么?
它是某一个key过时。能够对每个key单独设置过时时间,也能够对这些key统一设定过时时间。
本次的实现就简单点,统一设定key的过时时间,当key生存超过了这个时间,就自动被清除。
清除的时机,什么时候清除过时key?
用到某个key以前,先判断是否过时,若是过时从新调用函数生成新的key对应value值。
建立key以前,清除全部过时的key
value的设计
key=>(v,createtimestamp)适合key过时时间都是统一的设定
代码实现:
from functools import wraps #保留原有函数的名称和docstring import time import inspect import datetime def m_cache(duration): ##缓存函数装饰器 def _cache(fn): #duration缓存过时秒数 local_cache = {} #对不一样函数名是不一样的cache @wraps(fn) def wrapper(*args,**kwargs): # 接收各类参数,kwargs接收普通字典参数无序 #print(args,kwargs) ###判断local_cache有没有过时的key expire_keys = [] # for k,(_,ts) in local_cache.items(): if datetime.datetime.now().timestamp() - ts > duration: #当前时间减去时间戳大于设定描述秒过时 expire_keys.append(k) #找出过时的k加到列表里 for k in expire_keys: #遍历这些k local_cache.pop(k) #到字典里清除这些缓存 #借助inspect签名参数处理,构建key sig = inspect.signature(fn) params = sig.parameters #把签名里的参数的有序字典拿来辅助作有序位置参数用 param_list = list(params.keys()) #把有序字典的key放到列表里 #print(param_list) #['x', 'y'] #定义空字典为构造key帮忙用 key_dict = {} ###解决位置参数add(4,5) add(4,y=5) for i,v in enumerate(args): #i是索引,v是参数,有几个args就迭代几回,借助索引找到k所对应的的值 #print(i,v) k = param_list[i] #从参数有序字典['x', 'y']里拿到key,当i=0的时候拿到x,当i=1的时候拿到y key_dict[k] =v #把x,y对应的参数放到本身定义的字典里 ###解决关键字参数add(x=4,y=5),add(y=5,x=4) key_dict.update(kwargs) #把实参x=4,y=5直接放到自定义字典key_dict里 ###缺省值处理add(4) for k in params.keys(): #迭代只读有序字典,定义的多,传进来的少,说明没有key if k not in key_dict: #若是key没在这里就是没有经过实参传进来 key_dict[k] = params[k].default #找y的缺省值5填进来 ####最终把全部值key_dict排序 key = tuple(sorted(key_dict.items())) #key_dict是乱序的,经过sorted要对二元组进行排序,保证你们都同样,有了这个key作哈希 #print(key) #(('x', 4), ('y', 5)) if key not in local_cache.keys(): #key若是不在本地好缓存中 ret = fn(*args,**kwargs) #执行add函数后 #创建k和v之间的对应关系,把他的执行结果和时间戳填进去 local_cache[key] = (ret,datetime.datetime.now().timestamp()) #local_cache[key] = ret return local_cache[key] #若是local_cache有直接拿取 return wrapper return _cache #时间装饰器函数查看执行时间 def logger(fn): @wraps(fn) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() print(delta) return ret return wrapper @logger @m_cache(6) #带参装饰器传入6秒 def add(x,y=5): time.sleep(3) ret = x + y #print(ret) return ret #第一次执行后 print(add(4)) #后面都有缓存 print(add(4,5)) print(add(4,y=5)) print(add(x=4,y=5)) print(add(y=5,x=4)) #模拟等待六秒清除缓存 time.sleep(6) #清除缓存后调用 print(add(4)) #后面都有缓存 print(add(4,5)) #返回: 3.000171 (9, 1567060234.731866) 0.0 (9, 1567060234.731866) 0.0 (9, 1567060234.731866) 0.0 (9, 1567060234.731866) 0.0 (9, 1567060234.731866) 3.000172 (9, 1567060243.732381) 0.0 (9, 1567060243.732381)
2.写一个命令分发器
程序员能够方便注册函数到某一个命令,用户输入命令时,路由到注册的函数
若是此命令没有对应的注册函数,执行默认函数
用户输入用input(">>")
(1)分析:
输入命令映射到一个函数,并执行这个函数。应该是cmd_tbl[cmd] = fn的形式,字典正好适合。
若是输入了某一个cmd命令后,没有找到函数,就要调用缺省的函数执行,这正好是字典缺省参数
cmd是字符串
(2)封装
将reg函数封装成装饰器,并用它来注册函数
把字典,reg,dispatcher等封装起来,由于外面只要使用调度和注册就能够了
(3)代码实现:
def cmds_dispatcher(): #构造全局字典 commands = {} #命令和函数存储的地方 #注册装饰器函数 def reg(name): def _reg(fn): commands[name] = fn #把foo1和foo2放入到空字典中 #print(commands) return fn return _reg #缺省函数 def defaultfunc(): print("非法命令") #调度器函数 def dispatcher(): while True: cmd = input('>>') if cmd.strip() == 'quit': #若是等于quit return #退出 commands.get(cmd,defaultfunc)() #若是输入的不是quit,.get方法判断用户输入的cmd是否存在字典里,存在调用,若是不存在返回默认值缺省函数defaultfunc return reg,dispatcher #return出来作参数解构 reg,dispatcher = cmds_dispatcher() ###自定义函数 注册 @reg('xx') def foo1(): print('welcom xixi') @reg('dd') def foo2(): print('welcom dongdong') #调度循环 dispatcher() #执行: >>abc 非法命令 >>xx welcom xixi >>dd welcom dongdong >>quit
3.给四个函数index(),index2(),home(name),shopping_car(name)分别加上带参数(闭包)认证功能
#当前四个函数 def index(): print('欢迎来到西西主页') def index2(): print('再次来到西西主页') def home(name): print('欢迎回家%s' %name) def shopping_car(name): print('%s的购物车里有[%s,%s,%s]' %(name,'可乐','橙子','西瓜')) index() index2() home('西西') shopping_car('西西')
装饰器函数auth_func()代码:
user_list=[ {'name':'xixi','passwd':'123456'}, {'name':'shishi','passwd':'123456'}, ] current_dic={'username':None,'login':False} def auth(auth_type='filedb'): def auth_func(func): def wrapper(*args,**kwargs): print('认证类型是',auth_type) if auth_type == 'filedb': if current_dic['username'] and current_dic['login']: #第二步:判断当前状态是否有用户名和密码登陆,当前{'username': None, 'login': False}没有用户名且密码也不是True #第五步:当前用户名和密码登陆状态为{'username': 'xixi', 'login': True} 有用户名和密码直接执行 print('第二次记录登陆状态') #打印 res = func(*args, **kwargs) #跳过执行 #执行函数index2()函数 return res #跳过执行 #返回第二个函数index2()值:再次来到西西主页 #第三步:执行输入帐户和密码遍历字典查看字典里是否有账号和密码 username=input('用户名:').strip() #输出账号 passwd=input('密码:').strip() #输入密码 for user_dic in user_list: #遍历字典查看是否有输入的用户名和密码 if username == user_dic['name'] and passwd == user_dic['passwd']: #输入xixi和123456判断当前输入的用户名和密码跟字典当中的是否相等 current_dic['username']=username #若是相等状态改为记录用户状态 current_dic['login']=True #密码若是相等状态改为True res = func(*args, **kwargs) #执行函数index() return res #返回第一个函数index()值:欢迎来到西西主页 else: print('用户名或者密码错误') #第七步:认证ldap elif auth_type == 'ldap': print('ldap认证方式') #打印:ldap认证方式 res = func(*args, **kwargs) #执行函数index() return res #返回第三个函数home(name)值:欢迎回家西西 else: #第九步 print('其它认证方式') res = func(*args, **kwargs) return res return wrapper return auth_func @auth(auth_type='filedb') def index(): print('欢迎来到西西主页') @auth(auth_type='filedb') def index2(): print('再次来到西西主页') @auth(auth_type='ldap') def home(name): print('欢迎回家%s' %name) @auth(auth_type='aaaaaa') def shopping_car(name): print('%s的购物车里有[%s,%s,%s]' % (name, '可乐', '橙子', '西瓜')) index() #第一步:运行index()函数 index2() #第四步:运行index2()函数 home('西西') #第六步:运行home('西西') shopping_car('西西') #第八步:运行shopping_car('西西') #打印结果: 认证类型是 filedb 用户名:xixi 密码:123456 欢迎来到西西主页 认证类型是 filedb 第二次记录登陆状态 再次来到西西主页 认证类型是 ldap ldap认证方式 欢迎回家西西 认证类型是 aaaaaa 其它认证方式 西西的购物车里有[可乐,橙子,西瓜]
调用函数过程:
(1)调用最外层auth(auth_type='filedb')至关于直接来运行auth()函数,运行完了auth()函数就要要有一个返回值,这个返回值就是return auth_func
(2)这个auth(auth_type='filedb')的目的是给下面嵌套的子程序都让他们接收一个auth_type的值。运行这个函数拿到的返回值是auth_func内存地址,这个auth_func附加了一个auth_type的形参filedb
(3)auth_func作的事至关于auth_func(index)赋值给index,拿到index就能够运行index(),这个index至关于auth_func执行的结果,auth_func执行的结果是return wrapper,因此说index()执行的仍是wrapper
(4)运行index()直接走wrapper打印print('认证类型是',auth_type),这个auth_type认证类型是最外层给传的filedb有了认证类型就能够作判断