Python3 從零單排6_裝飾器&生成器&迭代器
1.裝飾器
明月裝飾了你的窗子,你裝飾了我的夢。所謂裝飾就是不改變原來實物本身,只是在事物之外給它加以裝飾。
在程式設計裡一樣,因為專案會一直有優化、更新,所以可能會對以前的功能進行優化,那麼開發的原則是“開放-封閉”。開放:允許在原有的功能上擴充套件功能;封閉:不允許修改原始碼。所以有了裝飾器,在不改變原始碼的情況下,增加功能。
裝飾器原理:
# 現在已經寫好了一個函式,現在需要得到它的執行時間 import time,random def sort_lis(n): lis = [i for i in range(n)] random.shuffle(lis)#打亂列表順序 lis.sort() # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多 print("done!") start_time = time.time() sort_lis(1000000) end_time = time.time() rum_time = end_time - start_time print(rum_time) #完美,我們拿到這個時間,但是如果有很多個這樣的函式需要獲得執行時間,每次呼叫都這樣去加程式碼顯然很low,而且違反了開發專案的封閉原則。
這個時候會思考,那麼我把這個獲取時間的程式碼,寫成一個函式,調sort_lis的時候,直接調這個函式就好了,但是要怎麼寫呢,因為sort_lis要在你的函式中執行才能拿到時間啊,於是寫出了這樣的程式碼
import time,random def get_run_time(func): start_time = time.time() res = func() end_time = time.time() rum_time = end_time - start_time print(rum_time) return res def sort_lis(): lis = [i for i in range(100000)] random.shuffle(lis) #打亂列表順序 lis.sort() # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多print("done!") get_run_time(sort_lis)
哈哈哈,太機智了,直接就拿到了這個執行時間,但是老鐵,1你改變了函式的呼叫方式,2.有沒發現萬一sort_lis這個函式有傳參怎麼辦,再這樣寫是不是涼涼了?於是想到了更進一步的辦法
import time,random def get_run_time(func): def wrapper(): start_time = time.time() res = func() end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper def sort_lis(): lis = [i for i in range(10000)] random.shuffle(lis) #打亂列表順序 lis.sort() # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多 print("done!") new_func = get_run_time(sort_lis) new_func()
這回你想沒啥問題了吧,但是還是上面兩點沒達到,別人之前是sort_lis()呼叫,現在變成了new_func()呼叫,函式名都被你改了,以前的那麼多程式碼都一個個去改麼,顯然又懵了,於是你想到了我直接用原函式變數名來命名不就好了麼?
import time,random def get_run_time(func): def wrapper(): start_time = time.time() res = func() end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper def sort_lis(): lis = [i for i in range(10000)] random.shuffle(lis) #打亂列表順序 lis.sort() # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多 print("done!") sort_lis = get_run_time(sort_lis) sort_lis()
看這樣我就實現了吧,既不改變原函式程式碼,也沒改變原來的呼叫方式,只是加了一句 sort_lis = get_run_time(sort_lis) 就搞定了,沒錯,這就是裝飾器的原理了,不過一般不這樣寫,用@,俗稱語法糖,如下:
import time,random def get_run_time(func): def wrapper(): start_time = time.time() res = func() end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper @get_run_time def sort_lis(): lis = [i for i in range(10000)] random.shuffle(lis) #打亂列表順序 lis.sort() # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多 print("done!") sort_lis()
那麼問題來了,要是被裝飾函式有傳參,上面的這個不是搞不定了嗎?
被裝飾函式有傳參:
import time,random def get_run_time(func): def wrapper(*args,**kwargs): start_time = time.time() res = func(*args,**kwargs) end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper @get_run_time def sort_lis(n): lis = [i for i in range(n)] random.shuffle(lis) #打亂列表順序 lis.sort() # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多 print("done!") sort_lis(10000)
裝飾器本身帶有傳參:等於是把上面的裝飾器再包了一層。
比如這裡你想讓測試人員嚇一跳,你可以這樣弄,一個功能相應時間多加sec秒,然後下次提測把不傳這個引數,相應時間瞬間就上去了,讓測試覺得你很厲害,嘿嘿。
import time,random def timer(sec=''): def get_run_time(func): def wrapper(*args, **kwargs): start_time = time.time() if sec: time.sleep(sec) res = func(*args, **kwargs) end_time = time.time() rum_time = end_time - start_time print(rum_time) return res return wrapper return get_run_time @timer(2) def sort_lis(n): lis = [i for i in range(n)] random.shuffle(lis) #打亂列表順序 lis.sort() # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多 print("done!") sort_lis(10000)
2.生成器
先來看下列表生成式:就是根據一個規則,生成一個列表。
lis = [i for i in range(10)] new_lis = [x**x for x in lis] print(lis,new_lis)
生成式很好用,但是當列表元素太多的時候,記憶體會吃不消,有沒有一種方法,我只做一個生產元素的模型,需要元素了我再去模型裡拿,每取一次給我生成一個元素,這樣就不佔記憶體,而且還達到了目的。是的,這個模型就是生成器。
生成器的建立和呼叫:
# 生成器的第一種建立方式:就是把上面的列表生成式的[]符號,改成(),就是一個生成器了: new_lis = (x**x for x in range(10)) print(new_lis) # <generator object <genexpr> at 0x0000000004034938> 這就是我們的生成器了 # 生成器的第二種建立方式:函式里加上 yield,函式就變成了一個生成器 def fib(max): n, a, b = 0, 0, 1 while n < max: # print(b) yield b a, b = b, a + b n = n + 1 return 'done' new_fib = fib(2) print(new_fib) # <generator object fib at 0x00000000040149E8> 這就是我們函式生成的生成器了 # 生成器的三種呼叫方式: print(next(new_fib)) # 直接next可以一次次的獲取生成器生成的元素,當取完最後一個元素時,會報錯:StopIteration: done print(new_fib.send("hello")) # 同next,只是可以傳遞一個訊號到生成器內部。 for i in new_fib: # 這個呼叫和迴圈去可迭代物件一樣,一般是用這種方法呼叫生成器,調完結束不會報錯。 print(i)
上面有說道訊號,這個訊號有啥用呢?
def fib2(max): n, a, b = 0, 0, 1 while n < max: # print(b) single = yield b #拿到send過來的訊號 if single == "stop": #如果訊號說stop,就跳出迴圈,不再生成元素 break a, b = b, a + b n = n + 1 return 'done' new_fib = fib2(7) print(next(new_fib)) print(new_fib.send("hello")) print(new_fib.send("stop")) #生成器在接到這個訊號的時候,直接不再返回元素,拋錯:StopIteration: done
3.迭代器
我們已經知道,可以直接作用於for迴圈的資料型別有以下幾種:一類是集合資料型別,如list、tuple、dict、set、str等;一類是generator,包括生成器和帶yield的generator function。
這些可以直接作用於for迴圈的物件統稱為可迭代物件:Iterable。可以使用isinstance()判斷一個物件是否是Iterable物件:
from collections import Iterable isinstance([], Iterable) # True isinstance({}, Iterable) # True isinstance('abc', Iterable) # True isinstance((x for x in range(10)), Iterable) # True isinstance(100, Iterable) # False
而生成器不但可以作用於for迴圈,還可以被next()函式不斷呼叫並返回下一個值,直到最後丟擲StopIteration錯誤表示無法繼續返回下一個值了。
*可以被next()函式呼叫並不斷返回下一個值的物件稱為迭代器:Iterator。可以使用isinstance()判斷一個物件是否是Iterator物件:
from collections import Iterator isinstance((x for x in range(10)), Iterator) # True isinstance([], Iterator) # False isinstance({}, Iterator) # False isinstance('abc', Iterator) # False
生成器都是Iterator物件,但list、dict、str雖然是Iterable,卻不是Iterator。
把list、dict、str等Iterable變成Iterator可以使用iter()函式:
isinstance(iter([]), Iterator) # True isinstance(iter('abc'), Iterator) # 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物件。
Python3的for迴圈本質上就是通過不斷呼叫next()函式實現的,例如:
for x in [1, 2, 3, 4, 5]: pass # 實際上完全等價於: # 首先獲得Iterator物件: it = iter([1, 2, 3, 4, 5]) # 迴圈: while True: try: # 獲得下一個值: x = next(it) except StopIteration: # 遇到StopIteration就退出迴圈 break