在學習python的時候,三大“名器”對沒有其他語言編程經驗的人來說,應該算是一個小難點,本次博客就博主自己對裝飾器、迭代器和生成器理解進行解釋。 為什麼要使用裝飾器 什麼是裝飾器?“裝飾”從字面意思來誰就是對特定的建築物內按照一定的思路和風格進行美化的一種行為,所謂“器”就是工具,對於python ...
在學習python的時候,三大“名器”對沒有其他語言編程經驗的人來說,應該算是一個小難點,本次博客就博主自己對裝飾器、迭代器和生成器理解進行解釋。
為什麼要使用裝飾器
什麼是裝飾器?“裝飾”從字面意思來誰就是對特定的建築物內按照一定的思路和風格進行美化的一種行為,所謂“器”就是工具,對於python來說裝飾器就是能夠在不修改原始的代碼情況下給其添加新的功能,比如一款軟體上線之後,我們需要在不修改源代碼和不修改被調用的方式的情況下還能為期添加新的功能,在python種就可以用裝飾器來實現,同樣在寫代碼的時候也要考慮到後面的可擴展性,下麵我們來看一步一步的看一下python的裝飾器。
一個簡單例子引入無參裝飾器
先來看簡單的幾行代碼,代碼的運行結果是先睡2秒,再列印"hello boy!":
import time def foo(): """列印""" time.sleep(2) print("Hello boy!") foo()
我們現在我們需要為其添加一個程式計時功能,但是不能修改原始的代碼:
import time def timmer(func): def wrapper(): """計時功能""" time_start=time.time() func() time_end=time.time() print("Run time is %f "%(time_end-time_start)) return wrapper def foo(): """列印""" time.sleep(2) print("Hello boy!") foo=timmer(foo) foo() #運行結果 Hello boy! Run time is 2.000446
看!我們沒有修改原來的代碼就實現了這個功能,因為函數也是對象,所以能夠將函數foo當做參數傳遞給了函數timmer。
在python中,有個更簡潔的方式來取代foo=timmer(foo),使用@timmer這種方式,這個在python中被稱為語法糖。
import time def timmer(func): def wrapper(): """計時功能""" time_start=time.time() func() time_end=time.time() print("Run time is %f "%(time_end-time_start)) return wrapper @timmer #等於 foo=timmer(foo) def foo(): """列印""" time.sleep(2) print("Hello boy!") foo()
下麵我們來一步一步的分析函數的執行過程:
1.導入time模塊
import time
2.定義函數timmer,定義函數並不會執行函數內的代碼
def timmer(func):
3.調用裝飾器,相當於foo=timer(foo),就是把函數foo作為參數穿給了函數timmer
@timmer
4.運行函數timmer,接受了參數 func=foo
def timmer(func):
5.在函數timmer內,定義了函數wrapper,wrapper函數內部代碼也不執行,然後將函數wrapper作為返回值返回
return wrapper
6.將返回值賦值給了foo,在第3步中,foo=timmer(foo),還記吧
@timmer #等於 foo=timmer(foo)
7.運行函數foo(),但是這裡的函數已經不是原來的那個函數了,可以列印foo,對的,因為之前我們將wrapper作為返回值傳給了foo,所以在這裡執行foo就是在執行wrapper了,為了再確定這一點你也可列印wrapper,它們的記憶體地址相同,所以都是指向同一個地址空間:
<function timmer.<locals>.wrapper at 0x00000180E0A8A950> #列印foo的結果 <function timmer.<locals>.wrapper at 0x000001F10AD8A950> #列印wrapper的結果
foo()
8.運行函數wrapper,記錄開始時間,執行函數func,在第4步的時候,func被foo賦值,運行func就是在運行原函數foo,睡2秒,列印字元串;
time_start=time.time()
time.sleep(2) print("Hello boy!")
9.記錄結束時間,列印運行時間,程式結束。
Hello boy! Run time is 2.000161
有參裝飾器
在前面的例子中,原函數沒有參數,下麵的來看一個當原函數有參數,該怎麼修改裝飾器函數呢?
import time def timmer(func): def wrapper(*args,**kwargs): """計時功能""" start_time=time.time() res=func(*args,**kwargs) end_time=time.time() print("Run time is %f"%(end_time-start_time)) return res return wrapper @timmer def my_max(x,y): """返回兩個值的最大值""" res=x if x > y else y time.sleep(2) return res res=my_max(1,2) print(res) #運行結果 Run time is 2.000175 2
當原函數有需要傳入參數的時候,在這個例子my_max有兩個位置形成需要傳入參數,只需要在wrapper上添加兩個形參,本例子中使用了可變參數(*args,**kwargs)也是可以的,這是@timmer就等於my_max(1,2)=timmer(my_max)
下麵我們來看一個帶有參數的裝飾器:
def auth(filetype): def auth2(func): def wrapper(*args,**kwargs): if filetype == "file": username=input("Please input your username:") passwd=input("Please input your password:") if passwd == '123456' and username == 'Frank': print("Login successful") func() else: print("login error!") if filetype == 'SQL': print("No SQL") return wrapper return auth2 @auth(filetype='file') #先先返回一個auth2 ==》@auth2 ==》 index=auth2(index) ==》 index=wrapper def index(): print("Welcome to China") index()
如果裝飾器本身有參數,就需要多一層內嵌函數,下麵我們一步一步分析執行流程:
1.定義函數auth
def auth(filetype):
2.調用解釋器,首先要運行函數auth(filetype='file')
@auth(filetype='file')
3.運行函數auth,定義了一個函數auth2,並作為返回值返回,那麼這個@auth(filetype='file')就等同於@auth2,等同於index=auth2(index)
def auth(filetype): def auth2(func): def wrapper(*args,**kwargs): return wrapper return auth2
4.auth2(index)執行,func=index,定義函數wrapper,並返回之,這時候index其實就是等於wrapper了
def wrapper(*args,**kwargs): return wrapper
5.當運行index,即運行wrapper,運行函數內部代碼,filetype=="file",提示用戶輸出用戶名和密碼,判斷輸入是否正確,如果正確,則執行函數func(),等於執行原來的index,列印
if filetype == "file": username=input("Please input your username:") passwd=input("Please input your password:") if passwd == '123456' and username == 'Frank': print("Login successful") func()
6.運行結果測試
Please input your username:Frank Please input your password:123456 Login successful Welcome to China
裝飾器也是可以被疊加的:
import time # def timmer(func): def wrapper(): """計時功能""" time_start=time.time() func() time_end=time.time() print("Run time is %f "%(time_end-time_start)) # print("---",wrapper) return wrapper def auth(filetype): def auth2(func): def wrapper(*args,**kwargs): if filetype == "file": username=input("Please input your username:") passwd=input("Please input your password:") if passwd == '123456' and username == 'Frank': print("Login successful") func() else: print("login error!") if filetype == 'SQL': print("No SQL") return wrapper return auth2 @timmer @auth(filetype='file') #先先返回一個auth2 ==》@auth2 ==》 index=auth2() ==》 index=wrapper def index(): print("Welcome to China") index() #測試結果 Please input your username:Frank Please input your password:123456 Login successful Welcome to China Run time is 7.966267
註釋優化
import time def timmer(func): def wrapper(): """計算程式運行時間""" start_time=time.time() func() end_time=time.time() print("Run time is %s:"%(end_time-start_time)) return wrapper @timmer def my_index(): """列印歡迎""" time.sleep(1) print("Welcome to China!") my_index() print(my_index.__doc__) #運行結果 Welcome to China! Run time is 1.0005640983581543: 計算程式運行時間View Code
當我們使用了裝飾器的時候,雖然沒有修改代碼本身,但是在運行的時候,比如上面這個例子,運行my_index其實在運行wrapper了,如果我們列印my_index的註釋信息,會列印wrapper()的註釋信息,那麼該怎麼優化?
可以在模塊functools中導入wraps,具體見以下:
import time from functools import wraps def timmer(func): @wraps(func) def wrapper(): """計算程式運行時間""" start_time=time.time() func() end_time=time.time() print("Run time is %s:"%(end_time-start_time)) return wrapper @timmer def my_index(): """列印歡迎""" time.sleep(1) print("Welcome to China!") my_index() print(my_index.__doc__) #運行結果 Welcome to China! Run time is 1.0003223419189453: 列印歡迎
這樣,在錶面看來,原函數沒有發生任何變化。
為什麼要用迭代器
從字面意思,迭代就是重覆反饋過程的活動,其目的通常是為了比較所需目標或結果,在python中可以用迭代器來實現,先來描述一下迭代器的優缺點,如果看不懂可以先略過,等看完本博客再回頭看,相信你會理解其中的意思:
優點:
迭代器在取值的時候是不依賴於索引的,這樣就可以遍歷那些沒有索引的對象,比如字典和文件
迭代器與列表相比,迭代器是惰性計算,更節省記憶體
缺點:
無法獲取迭代器的長度,沒有列表靈活
只能往後取值,不能倒著取值
什麼是迭代器
那麼在python什麼才算是迭代器呢?
只要對象有__iter__(),那麼它就是可迭代的,迭代器可以使用函數next()來取值
下麵我們來看一個簡單的迭代器:
my_list=[1,2,3] li=iter(my_list) #li=my_list.__iter__() print(li) print(next(li)) print(next(li)) print(next(li)) #運行結果 <list_iterator object at 0x000002591652C470> 1 2 3
可以看到,使用內置函數iter可以將列表轉換成一個列表迭代器,使用next()獲取值,一次值取一個值,當值取完了,再使用一次next()的時候,會報異常StopIteration,可以通過異常處理的方式來避免,try-except-else就是一個最常用的異常處理結構:
my_list=[1,2,3] li=iter(my_list) while True: try: print(next(li)) except StopIteration: print("Over") break else: print("get!") #運行結果 1 get! 2 get! 3 get! Over
我們學過的for迴圈其實就是在對象後面加上了方法__iter__(),使對象成為了一個迭代器,然後再一一遍歷其中的值,使用迭代器一次一次的next(),每次在記憶體中只會占一個值的空間。
查看可迭代對象和迭代器對象
使用Iterable模塊可以判斷對象是否是可迭代的:
from collections import Iterable s="hello" #定義字元串 l=[1,2,3,4] #定義列表 t=(1,2,3) #定義元組 d={'a':1} #定義字典 set1={1,2,3,4} #定義集合 f=open("a.txt") #定義文本 # 查看是否都是可迭代的 print(isinstance(s,Iterable)) print(isinstance(l,Iterable)) print(isinstance(t,Iterable)) print(isinstance(d,Iterable)) print(isinstance(set1,Iterable)) print(isinstance(f,Iterable)) #運行結果 True True True True True True
通過判斷,可以確定我們所知道的常用的數據類型都是可以被迭代的。
使用Iterator模塊可以判斷對象是否是迭代器:
from collections import Iterable,Iterator s="hello" l=[1,2,3,4] t=(1,2,3) d={'a':1} set1={1,2,3,4} f=open("a.txt") # 查看是否都是可迭代的 print(isinstance(s,Iterator)) print(isinstance(l,Iterator)) print(isinstance(t,Iterator)) print(isinstance(d,Iterator)) print(isinstance(set1,Iterator)) print(isinstance(f,Iterator)) #運行結果 False False False False False True
可知只有文件是迭代器,所以可以直接使用next(),而不需要轉換成迭代器。
什麼是生成器
生產器就是一個是帶有yield的函數
下麵來看一個簡單的生成器
def my_yield(): print('first') yield 1 g=my_yield() print(g) #運行結果 <generator object my_yield at 0x0000024366D7E258>
生成器也是一個迭代器
from collections import Iterator def my_yield(): print('first') yield 1 g=my_yield() print(isinstance(g,Iterator)) #運行結果 True
那就可以用next()來取值了
print(next(g)) #運行結果 first 1
生成器的執行過程
我們來看以下下麵這個例子,瞭解生產的執行流程
def my_yield(): print('first') yield 1 print('second') yield 2 print('Third') yield 3 g=my_yield() next(g) next(g) next(g) #運行結果 first second Third
1.定義生成器my_yield,並將其賦值給了g
def my_yield(): g=my_yield()
2.開始第一次執行next(),開始執行生產器函數 ,列印第一語句,遇到yileld的時候暫停,並返回一個1,如果你想列印返回值的話,這裡會顯示1
print('first') yield 1
3.再執行2次,列印字元串(每執行一次都會暫停一下)
print('second') yield 2 print('Third') yield 3
4.如果再加一次next()就會報出StopIteration異常了
生成器在每次暫停的時候,函數的狀態將被保存下來,來看下麵的例子:
def foo(): i=0 while True: yield i i+=1 g=foo() for num in g: if num < 10: print(num) else: break #運行結果 0 1 2 3 4 5 6 7 8 9
for迴圈中隱含next(),每next一次,暫停一次,if語句判斷一次,然後執行下一次next,可以看到我們的while迴圈並沒有無限迴圈下去,而是狀態被保存下來了。
協程函數
我們來看下麵這個生成器和執行結果
def eater(name): print('%s start to eat food'%name) while True: food=yield print('%s get %s ,to start eat'%(name,food)) print('done') e=eater('Frank') next(e) e.send('egg') #給yield送一個值,並繼續執行代碼 e.send('tomato') #運行結果 Frank start to eat food Frank get egg ,to start eat Frank get tomato ,to start eat
send可直接以向yield傳值,含有yield表達式的函數我們也稱為協程函數,
這運行程式的時候,不可以直接send,必須先使用next()初始化生成器。
如果存在多個這樣的函數,那麼我們每次執行的時候都要去next()一下,為了防止忘記這一步操作,可以使用裝飾器初始化:
def init(func): def wrapper(*args): res = func(*args) next(res) # 在這裡執行next return res return wrapper @init def eater(name): print('%s start to eat food'%name) while True: food=yield print('%s get %s ,to start eat'%(name,food)) print('done') e=eater('Frank') e.send('egg') e.send('tomato')
所以在程式中有更多的生成器需要初始化的時候,直接調用這個裝飾器就可以了。