8.python迭代器--生成器--函數式裝飾器

迭代器
一.迭代器瞭解
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有了認證類型就能夠作判斷

相關文章
相關標籤/搜索