參考鏈接: 有效地在Python中使用迭代
文章目錄
?生成器生成器表達式(generator expression)通過使用yield關鍵字定義生成器并行前戲高潮
??
? 迭代器迭代器概述iter()函數 創建迭代器創建一個迭代器(類)內置迭代器工具count無限迭代器cycle 無限迭代器,從一個有限序列中生成無限序列:itertools的子模塊 islice 控制無限迭代器輸出的方式
??
? 裝飾器高階函數嵌套函數高階函數+嵌套函數 = 裝飾器類裝飾器帶參數的decorator實例---登錄認證
?
?
?
生成器?
通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數元素占用的空間都白白浪費了。?
所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出后續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。?
要創建一個generator,有很多種方法。第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個generator:?
生成器表達式(generator expression)?
L = [x + 1 for x in range(10)]
print(L)
?
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
?
列表生成式復習?
實現列表元素加1,列表生成式與其它方法比較:?
#普通方法1
b = []
for i in range(10):
? ? b.append(i+1)
print(b)
?
#普通方法2
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for index,i in enumerate(a):
? ? a[index] +=1
print(a)
?
#map,lambda
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a = map(lambda x:x+1, a)
print(list(a))
?
?
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
?
g = (x + 1 for x in range(10))
print(g)
?
<generator object <genexpr> at 0x7fe03ad859a8>
?
創建L和g的區別僅在于最外層的[]和(),L是一個list,而g是一個generator。?
我們可以直接打印出list的每一個元素,但我們怎么打印出generator的每一個元素呢??
如果要一個一個打印出來,可以通過next()函數(or __next__())獲得generator的下一個返回值:?
next(g)
?
1
?
next(g)
?
2
?
next(g)
?
3
?
g.__next__()
?
4
?
g.__next__()
?
5
?
generator保存的是算法,每次調用next(g),就計算出g的下一個元素的值,直到計算到最后一個元素,沒有更多的元素時,拋出StopIteration的錯誤?
上面這種不斷調用next(g)實在是太變態了,正確的方法是使用for循環,因為generator也是可迭代對象:?
g = (x * x for x in range(10))
for n in g:
? ? print(n,end=";")
?
0;1;4;9;16;25;36;49;64;81;
?
所以,我們創建了一個generator后,基本上永遠不會調用next(),而是通過for循環來迭代它,并且不需要關心StopIteration的錯誤?
通過使用yield關鍵字定義?
生成器對象是通過使用yield關鍵字定義的函數對象,因此,生成器也是一個函數。生成器用于生成一個值得序列,以便在迭代器中使用。?
"""
第一是直接作為腳本執行,
第二是import到其他的python腳本中被調用(模塊重用)執行。
因此if __name__ == '__main__': 的作用就是控制這兩種情況執行代碼的過程,
在if __name__ == '__main__':下的代碼只有在第一種情況下(即文件作為腳本直接執行)才會被執行,而import到其他腳本中是不會被執行的。
"""
?
def myYield(n):
? ? while n>0:
? ? ? ? print('開始生成。。。')
? ? ? ? yield n
? ? ? ? print('完成一次。。。')
? ? ? ? n -= 1
if __name__ == '__main__':
? ? a = myYield(3)
? ? print('已經實例化生成器對象')
#? ? ?a.__next__()
#? ? ?print('第二次調用__next__()方法:')
#? ? ?a.__next__()
?
已經實例化生成器對象
?
yield 語句是生成器中的關鍵語句,生成器在實例化時并不會被執行,而是等待調用其__next__()方法才開始運行。并且當程序運行完yield語句后就會“吼(hold)住”,即保持當前狀態且停止運行,等待下一次遍歷時才恢復運行。?
程序運行的結果中的空行后的輸出“已經實例化生成器對象”之前,已經實例化了生成器對象,但生成器并沒有運行(沒有輸出‘開始生成’)。當第一次手工調用__next__()方法之后,才輸出‘開始生成’,標志著生成器已經運行,而在輸出‘’第二次調用__next__()方法‘’之前并沒有輸出‘完成一次’,說明yield語句運行之后就立即停止了。而第二次調用__next__()方法之后,才輸出‘完成一次’,說明生成器的回復運行是從yield語句之后開始運行的?
return_value = a.__next__()
print(return_value)
?
開始生成。。。
3
?
print('第二次調用__next__()方法:')
?
第二次調用__next__()方法:
?
return_value = a.__next__()
print(return_value)
?
完成一次。。。
開始生成。。。
2
?
著名的斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數都可由前兩個數相加得到:?
斐波拉契數列用列表生成式寫不出來,但是,用函數把它打印出來卻很容易:?
def fib(max):
? ? n, a, b = 0, 0, 1
? ? while n < max:
? ? ? ? print(b)
? ? ? ? a, b = b, a + b
? ? ? ? n = n + 1
? ? return 'done'
?
注意,賦值語句:?
a, b = b, a + b
?
相當于:?
t = (b, a + b) # t是一個tuple
a = t[0]
b = t[1]
?
上面的函數可以輸出斐波那契數列的前N個數:?
fib(5)
?
1
1
2
3
5
?
?
?
?
?
'done'
?
上面的函數和generator僅一步之遙。要把fib函數變成generator,只需要把print(b)改為yield b就可以了:?
def fib(max):
? ? n,a,b = 0,0,1
? ? while n < max:
? ? ? ? #print(b)
? ? ? ? yield? b
? ? ? ? a,b = b,a+b
? ? ? ? n += 1
? ? return 'well done'?
?
這里,最難理解的就是generator和函數的執行流程不一樣。函數是順序執行,遇到return語句或者最后一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。?
f = fib(5)
print(f)
print(list(f))
?
#重新實例化生成器對象
f = fib(5)
?
<generator object fib at 0x7fe038493840>
[1, 1, 2, 3, 5]
?
print(f.__next__())
print(f.__next__())
print("干點別的事")
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
?
1
1
干點別的事
2
3
5
?
?
?
---------------------------------------------------------------------------
?
StopIteration? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Traceback (most recent call last)
?
<ipython-input-18-9609f54647c6> in <module>
? ? ? 5 print(f.__next__())
? ? ? 6 print(f.__next__())
----> 7 print(f.__next__())
?
?
StopIteration: well done
?
用for循環調用generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:?
g = fib(6)
while True:
? ? try:
? ? ? ? x = next(g)
? ? ? ? print('g:', x)
? ? except StopIteration as e:
? ? ? ? print('Generator return value:', e.value)
? ? ? ? break
?
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: well done
?
from itertools import islice
def fib():
? ? a,b = 0,1
? ? while True:
? ? ? ? yield b
? ? ? ? a,b = b,a+b
f = fib()
print(list(islice(f ,0,10)))
?
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
?
生成器在Python中是一個非常強大的編程結構,可以用更少地中間變量寫流式代碼,此外,相比其它容器對象它更能節省內存和CPU,當然它可以用更少的代碼來實現相似的功能。現在就可以動手重構你的代碼了,但凡看到類似?
def something():
? ? result= []
? ? for ... in ...:
? ? ? ? result.append(x)
? ? ? ? return result
?
都可以用生成器函數來替換:?
def iter_something():
? ? result = []
? ? for ... in ...:
? ? ? ? yield x
?
楊輝三角?
期待輸出:?
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
?
def triangles():
? ? result = [1]
? ? while True:
? ? ? ? yield result
? ? ? ? result = [1] + [result[i] + result[i+1] for i in range(len(result)-1)] + [1]
?
n = 0
results = []
for t in triangles():
? ? print(t)
? ? results.append(t)
? ? n = n + 1
? ? if n == 10:
? ? ? ? break
if results == [
? ? [1],
? ? [1, 1],
? ? [1, 2, 1],
? ? [1, 3, 3, 1],
? ? [1, 4, 6, 4, 1],
? ? [1, 5, 10, 10, 5, 1],
? ? [1, 6, 15, 20, 15, 6, 1],
? ? [1, 7, 21, 35, 35, 21, 7, 1],
? ? [1, 8, 28, 56, 70, 56, 28, 8, 1],
? ? [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
]:
? ? print('測試通過!')
else:
? ? print('測試失敗!')
?
?
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
測試通過!
?
生成器并行?
前戲?
def gen():
? ? a = yield 1
? ? print('yield a % s' % a)
? ? b = yield 2
? ? print('yield b % s' % b)
? ? c = yield 3
? ? print('yield c % s' % c)
? ? return "happy ending"
?
?
r = gen()
x = next(r)
print('next x %s' % x)
y = r.send(10)
print('next y %s' %y)
z = next(r)
print('next z %s' % z)
try:
? ? a = next(r)
except StopIteration as e:
? ? print(e)
?
?
next x 1
yield a 10
next y 2
yield b None
next z 3
yield c None
happy ending
?
運行過程說明:?
第一步:r = gen(),實例化一個生成器對象第二步:調用next() ,遇到yield 暫停,返回值1,賦值給x第三步:打印x的值第四步:傳值10,在暫停處接受值10,賦值給a,繼續運行,打印a的值,遇到第二個yield,暫停,返回值2,賦值給y第五步:打印y的值第六步:調用next() ,打印b值,遇到第三個yield暫停,返回值3,賦值給z第七步:打印z值第八步:調用next(),打印c的值,報StopIteration錯誤,用try。。。except捕獲錯誤?
高潮?
import time
import random
?
food = ["韭菜雞蛋","豬肉白菜","豬肉薺菜","羊肉白菜","豬肉大蔥","蝦仁海鮮"]
?
?
def consumer(name):
? ? print("%s 準備吃包子啦!" %name)
? ? while True:
? ? ? ? baozi = yield 'n'
? ? ? ? print("[%s]餡包子來了,被[%s]吃了!" %(baozi,name))
?
? ? ? ??
def producer(name):
? ? c1 = consumer('大兒子')
? ? c2 = consumer('小兒子')
? ? c1.__next__()
? ? c2.__next__()
? ? print("%s開始準備做包子啦" % name)
? ? for i in range(6):
? ? ? ? print("第%d次做了%s個包子"%(i+1,len(food)))
? ? ? ? time.sleep(random.randint(1,3))
? ? ? ? f1 = food[i]
? ? ? ? c1.send(f1)
? ? ? ? food.append(f1)
? ? ? ? random.shuffle(food)
? ? ? ? c2.send(food[i])
? ? ? ??
producer('老子')
?
大兒子 準備吃包子啦!
小兒子 準備吃包子啦!
老子開始準備做包子啦
第1次做了6個包子
[韭菜雞蛋]餡包子來了,被[大兒子]吃了!
[韭菜雞蛋]餡包子來了,被[小兒子]吃了!
第2次做了7個包子
[韭菜雞蛋]餡包子來了,被[大兒子]吃了!
[豬肉大蔥]餡包子來了,被[小兒子]吃了!
第3次做了8個包子
[韭菜雞蛋]餡包子來了,被[大兒子]吃了!
[豬肉大蔥]餡包子來了,被[小兒子]吃了!
第4次做了9個包子
[豬肉白菜]餡包子來了,被[大兒子]吃了!
[羊肉白菜]餡包子來了,被[小兒子]吃了!
第5次做了10個包子
[蝦仁海鮮]餡包子來了,被[大兒子]吃了!
[韭菜雞蛋]餡包子來了,被[小兒子]吃了!
第6次做了11個包子
[韭菜雞蛋]餡包子來了,被[大兒子]吃了!
[蝦仁海鮮]餡包子來了,被[小兒子]吃了!
?
迭代器?
迭代器概述?
可以直接作用于for循環的數據類型有以下幾種:?
一類是集合數據類型,如list,tuple,dict,set,str等一類是generator ,包括生成器和帶yeild的generator function?
這些可以 直接作用于for循環的對象統稱為可迭代對象:Iterable?
可以被next()函數調用并不斷返回下一個值的對象稱為迭代器:Iterator?
a = [i for i in range(10)]
next(a)
?
---------------------------------------------------------------------------
?
TypeError? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Traceback (most recent call last)
?
<ipython-input-23-8981550fe3e0> in <module>
? ? ? 1 a = [i for i in range(10)]
----> 2 next(a)
?
?
TypeError: 'list' object is not an iterator
?
list,dict,str雖然是Iterable,卻不是Iterator?
from collections import Iterator
from collections import Iterable
print(isinstance(a,Iterator))
print(isinstance(a,Iterable))
print(isinstance({},Iterable))
print(isinstance('abc',Iterable))
?
False
True
True
True
?
生成器就是一個迭代器?
a = (i for i in range(10))
print(next(a))
print(isinstance(a,Iterator))
?
0
True
?
iter()函數 創建迭代器?
iter(iterable)#一個參數,要求參數為可迭代的類型?
把list、dict、str等Iterable變成Iterator可以使用iter()函數:?
print(isinstance({},Iterator))
print(isinstance('abc',Iterator))
print(isinstance(iter({}),Iterator))
print(isinstance(iter('abc'),Iterator))
?
False
False
True
True
?
你可能會問,為什么list、dict、str等數據類型不是Iterator??
這是因為Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用并不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。?
Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。?
小結?
?凡是可作用于for循環的對象都是Iterable類型;? 凡是可作用于next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;? 集合數據類型如list、dict、str等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象。??
Python的for循環本質上就是通過不斷調用next()函數實現的,例如:?
for x in [1, 2, 3, 4, 5]:
? ? print(x,end=',')
?
1,2,3,4,5,
?
實際上完全等價于:?
# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5])
# 循環:
while True:
? ? try:
? ? ? ? # 獲得下一個值:
? ? ? ? x = next(it)
? ? ? ? print(x,end=',')
? ? except StopIteration:
? ? ? ? # 遇到StopIteration就退出循環
? ? ? ? break
?
?
1,2,3,4,5,
?
創建一個迭代器(類)?
把一個類作為一個迭代器使用需要在類中實現兩個方法 __iter__() 與 __next__() 。?
如果你已經了解的面向對象編程,就知道類都有一個構造函數,Python 的構造函數為 __init__(), 它會在對象初始化的時候執行?
__iter__() 方法返回一個特殊的迭代器對象, 這個迭代器對象實現了 __next__() 方法并通過 StopIteration 異常標識迭代的完成。?
from itertools import islice
class Fib:
? ? def __init__(self):
? ? ? ? self.prev = 0
? ? ? ? self.curr = 1
? ? def __iter__(self):
? ? ? ? return self
? ? def __next__(self):
? ? ? ? self.prev,self.curr = self.curr,self.prev+self.curr
? ? ? ? return self.curr
f = Fib()
print(list(islice(f ,0,10)))
?
[1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
?
Fib既是一個可迭代對象(因為它實現了 __iter__方法),又是一個迭代器(因為實現了 __next__方法)?
StopIteration?
StopIteration 異常用于標識迭代的完成,防止出現無限循環的情況,在 next() 方法中我們可以設置在完成指定循環次數后觸發 StopIteration 異常來結束迭代。?
在 20 次迭代后停止執行:?
class MyNumbers:
? ? def __init__(self):
? ? ? ? self.a = 1
? ??
? ? def __iter__(self):
? ? ? ? return self
?
? ? def __next__(self):
? ? ? ? if self.a <= 20:
? ? ? ? ? ? x = self.a
? ? ? ? ? ? self.a += 1
? ? ? ? ? ? return x
? ? ? ? else:
? ? ? ? ? ? raise StopIteration
?
myclass = MyNumbers()
myiter = MyNumbers()
# myiter = iter(myclass)
?
for x in myiter:
? ? print(x,end=",")
?
?
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
?
內置迭代器工具?
比如 itertools 函數返回的都是迭代器對象?
count無限迭代器?
from itertools import count
counter = count(start=10)??
print(next(counter))
print(next(counter)) #python內建函數next()對itertools創建的迭代器進行循環
?
10
11
?
cycle 無限迭代器,從一個有限序列中生成無限序列:?
from itertools import cycle
colors = cycle(['red','black','blue'])
print(next(colors))
print(next(colors))
print(next(colors))
print(next(colors))
print(next(colors))??
?
red
black
blue
red
black
?
itertools的子模塊 islice 控制無限迭代器輸出的方式?
islice的第二個參數控制何時停止迭代,迭代了11次后停止,從無限的序列中生成有限序列:?
from itertools import count
counter = count(start=10)
i=4
print(next(counter))
while i > 0:
? ? print(next(counter))
? ? i -= 1
?
10
11
12
13
14
?
from itertools import count
for i in count(10):
? ? if i > 14 :
? ? ? ? break
? ? else:
? ? ? ? print(i)
?
10
11
12
13
14
?
from itertools import islice
from itertools import count
for i in islice(count(10),5):
? ? print(i)
?
10
11
12
13
14
?
from itertools import cycle
from itertools import islice
colors = cycle(['red','black','blue'])
limited = islice(colors,0,4)
for x in limited:
? ? print(x)
?
red
black
blue
red
?
裝飾器?
器,代表函數的意思?
?裝飾器:本質是函數(裝飾其他函數)就是為其他函數添加附加功能?
每個人都有的內褲主要功能是用來遮羞,但是到了冬天它沒法為我們防風御寒,咋辦?我們想到的一個辦法就是把內褲改造一下,讓它變得更厚更長,這樣一來,它不僅有遮羞功能,還能提供保暖,不過有個問題,這個內褲被我們改造成了長褲后,雖然還有遮羞功能,但本質上它不再是一條真正的內褲了。于是聰明的人們發明長褲,在不影響內褲的前提下,直接把長褲套在了內褲外面,這樣內褲還是內褲,有了長褲后寶寶再也不冷了。裝飾器就像我們這里說的長褲,在不影響內褲作用的前提下,給我們的身子提供了保暖的功效。?
原則:?
1 不能修改被裝飾的函數的源代碼2 不能修改被裝飾的函數的調用方式?
實現裝飾器知識儲備:?
?1 函數即“”變量“”? 2 高階函數 a 把一個函數名當做實參傳給另一個函數 b 返回值中包含函數名??
高階函數?
import time
def bar():
? ? time.sleep(3)
? ? print('in the bar')
def test2(func):
? ? print(func)
? ? return func
?
print(test2(bar)) #調用test2,打印bar的內存地址,返回bar的內存地址,又打印
?
<function bar at 0x7fe03849e620>
<function bar at 0x7fe03849e620>
?
bar=test2(bar) # 返回的bar的內存地址,賦值給bar
bar() #run bar
?
<function bar at 0x7fe03849e620>
in the bar
?
嵌套函數?
x = 0
def grandpa():
? ? x = 1
? ? print(x)
? ? def dad():
? ? ? ? x =2
? ? ? ? print(x)
? ? ? ? def son():
? ? ? ? ? ? x =3
? ? ? ? ? ? print(x)
? ? ? ? son()
? ? dad()
?
grandpa()
?
1
2
3
?
高階函數+嵌套函數 = 裝飾器?
import time
def timer(func): #timer(test1) func=test1
? ? def deco(*args,**kwargs):? #非固定參數
? ? ? ? start_time=time.time()
? ? ? ? func(*args,**kwargs) #run test1()
? ? ? ? stop_time = time.time()
? ? ? ? print("the func run time is %s" %(stop_time-start_time))
? ? return deco
@timer #test1=timer(test1) 把deco的內存地址返回給test1
def test1():
? ? time.sleep(1)
? ? print('in the test1')
?
@timer # test2 = timer(test2) = deco test2(name) =deco(name)
def test2(name,age):
? ? print("test2:",name,age)
?
test1() #實際上是在執行deco
test2("alex",22)
?
in the test1
the func run time is 1.001246452331543
test2: alex 22
the func run time is 0.00011372566223144531
?
類裝飾器?
沒錯,裝飾器不僅可以是函數,還可以是類,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器主要依靠類的__call__方法?
?
class Foo(object):
? ? def __init__(self, func):
? ? ? ? self._func = func
?
? ? def __call__(self):
? ? ? ? print ('class decorator runing')
? ? ? ? self._func()
? ? ? ? print ('class decorator ending')
?
@Foo
def bar():
? ? print ('bar')
bar()
?
?
class decorator runing
bar
class decorator ending
?
裝飾器可以把與業務邏輯無關的代碼抽離出來,讓代碼保持干凈清爽,而且裝飾器還能被多個地方重復利用。比如一個爬蟲網頁的函數,如果該 URL 曾經被爬過就直接從緩存中獲取,否則爬下來之后加入到緩存,防止后續重復爬取。?
def web_lookup(url, saved={}):
? ? if url in saved:
? ? ? ? return saved[url]
? ? page = urllib.urlopen(url).read()
? ? saved[url] = page
? ? return page
?
pythonic?
import urllib.request as urllib # py3
def cache(func):
? ? saved= {}
? ? def wrapper(url):
? ? ? ? if url in saved:
? ? ? ? ? ? return saved [url]
? ? ? ? else:
? ? ? ? ? ? page = func(url)
? ? ? ? ? ? saved [url] = page
? ? ? ? ? ? return page
? ? return wrapper
? ??
@cache
def web_lookup(url):
? ? return urllib.urlopen(url).read()
?
用裝飾器寫代碼表面上感覺代碼量更多,但是它把緩存相關的邏輯抽離出來了,可以給更多的函數調用,這樣總的代碼量就會少很多,而且業務方法看起來簡潔了。?
帶參數的decorator?
def log(text):
? ? def decorator(func):
? ? ? ? def wrapper(*args, **kw):
? ? ? ? ? ? print('%s %s():' % (text, func.__name__))
? ? ? ? ? ? return func(*args, **kw)
? ? ? ? return wrapper
? ? return decorator
?
還差最后一步。因為我們講了函數也是對象,它有__name__等屬性,但你去看經過decorator裝飾之后的函數,它們的__name__已經從原來的’now’變成了’wrapper’:?
@log('execute')
def now():
? ? print('2015-3-25')
? ??
now()
now.__name__
?
execute now():
2015-3-25
?
?
?
?
?
'wrapper'
?
因為返回的那個wrapper()函數名字就是’wrapper’,所以,需要把原始函數的__name__等屬性復制到wrapper()函數中,否則,有些依賴函數簽名的代碼執行就會出錯。?
不需要編寫wrapper.__name__ = func.__name__這樣的代碼,Python內置的functools.wraps就是干這個事的,所以,一個完整的decorator的寫法如下:?
import functools
?
def log(text):
? ? def decorator(func):
? ? ? ? @functools.wraps(func)
? ? ? ? def wrapper(*args, **kw):
? ? ? ? ? ? print('%s %s():' % (text, func.__name__))
? ? ? ? ? ? return func(*args, **kw)
? ? ? ? return wrapper
? ? return decorator
?
?
@log('execute')
def now():
? ? print('2015-3-25')
? ??
now()
now.__name__
?
execute now():
2015-3-25
?
?
?
?
?
'now'
?
那么不帶參數decorator,也是一樣的?
import functools
?
def log(func):
? ? @functools.wraps(func)
? ? def wrapper(*args, **kw):
? ? ? ? print('call %s():' % func.__name__)
? ? ? ? return func(*args, **kw)
? ? return wrapper
?
實例—登錄認證?
import functools
?
?
user,passwd = 'sun' ,'123'
def auth(auth_type):
? ? print("auth func:",auth_type)
? ? def decorator(func):
? ? ? ? @functools.wraps(func)
? ? ? ? def wrapper(*args,**kwargs):
? ? ? ? ? ? print('wrapper func args:',*args,**kwargs)
? ? ? ? ? ? if auth_type == 'local':
? ? ? ? ? ? ? ? username = input('Username:').strip()
? ? ? ? ? ? ? ? password = input("Password:").strip()
? ? ? ? ? ? ? ? if user == username and passwd == password:
? ? ? ? ? ? ? ? ? ? print("\033[32;1mUser has passed authentication\033[0m")
? ? ? ? ? ? ? ? ? ? res = func(*args, **kwargs)?
? ? ? ? ? ? ? ? ? ? print("--after authentication--")
? ? ? ? ? ? ? ? ? ? return res
? ? ? ? ? ? ? ? else:
? ? ? ? ? ? ? ? ? ? exit("\033[31;1mInvalid username or password\033[0m")
? ? ? ? ? ? elif auth_type == 'ldap':
? ? ? ? ? ? ? ? res = func(*args, **kwargs)
? ? ? ? ? ? ? ? print("搞毛線ldap,不會。。。。")
? ? ? ? ? ? ? ? return res
?
? ? ? ? return wrapper
? ? return decorator
?
def index():
? ? print("welcome to index page")
?
@auth(auth_type='local')
def home():
? ? print("welcome to home page")
? ? return 'from home'
?
@auth(auth_type='ldap')
def bbs():
? ? print("welcome to bbs page")
?
?
index()
print(home())? #wrapper
bbs()
?
?
auth func: local
auth func: ldap
welcome to index page
wrapper func args:
Username:sun
Password:123
[32;1mUser has passed authentication[0m
welcome to home page
--after authentication--
from home
wrapper func args:
welcome to bbs page
搞毛線ldap,不會。。。。