生成器、裝飾器、叠代器
一、生成器
本來我們可以通過列表生成直接生成一個列表,為什麽要用生成器呢?我們都知道列表的數據會被全部加在到內存,然後可以通過下標進行訪問數據,如果我們要的數據量非常大呢,豈不是很占用內存,所以我們需要生成器,生成器呢是我們需要哪個數據就會生成哪個數據,也就是懶加載,只有當我們使用數據的時候才會被生成,這樣就是一邊循環,一邊計算數據,這個就叫做生成器:generator。但是只能按順序生成,因為會根據上一步的返回進行生成,這樣我們就可以節約內存了。
接下來我們看一個小列子:生成列表和生成器的創建區別,創建L
和g
的區別僅在於最外層的[]
和()
,L
是一個list,而g
是一個generator
>>> l = [i*2 for i in range(10)] #直接生成列表,會把數據全部加載 >>> l [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] >>> g = (i*2 for i in range(10)) #生成一個生成器則會返回個內存地址,然後可以通過next方法一個一個的取值,如果很多後面的值不需要使用,我們就可以節約很多內存 >>> g <generator object <genexpr> at 0x0000010E2E6A5DB0> >>> next(g) 0>>> next(g) 2 >>> next(g) 4 >>> next(g) 6 >>> next(g) 8 >>> next(g) 10
>>> next(g)
12
>>> next(g)
14
>>> next(g)
16
>>> next(g)
18
>>> next(g) #每次調用next方法,就會計算出下一個元素的值,直到計算出最後一個就會跑出StopIteration的異常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
當然除了用next獲取生成器的值,我們還可以使用for循環來獲取,所以我們不建議使用next方法,那個太麻煩了,使用for循環的話也不用擔心取得元素超出範圍拋出StopIteration異常:
>>> g = (x **2 for x in range(5)) >>> g <generator object <genexpr> at 0x0000010E2E6A5570> >>> for i in g: ... print(i) ... 0 1 4 9 16
當然除了使用類似於生成列表的方式生成生成器,我們還可以使用函數來生成,我們都知道斐波拉契數列,除了前面兩個元素,每個值都是前兩個的值相加得到。
>>> def fib(n): ... i,a,b = 0,0,1 ... while i<n: ... print(b) ... a,b = b,a+b ... i += 1 ... return "done" ... >>> fib(5) 1 1 2 3 5
如上就是斐波拉契數列的非遞歸式代碼,那麽我們如何把這個函數變成一個生成器呢,從代碼看來,我們產生的數據都是b,所以我們只要在輸出b的代碼行稍加更改動就可以。
>>> def fib(n): ... i,a,b = 0,0,1 ... while i<n: ... #print(b) yield b #修改成yield b即可 ... a,b = b,a+b ... i += 1 ... return "done" ...
>>> fib(5)
<generator object fib at 0x0000010E2E6A5D00
就是這麽神奇,這就是generator的另一種生成方法,如果一個函數中包含yield關鍵字,那麽函數不再是一個普通的函數了,而是一個generator,不考慮多線程,我們都知道函數是按順序執行的,但是generator是不一樣的,在每次調用next()的時候,遇到yield就會返回,再一次執行從上次返回的yield語句處執行。
def fib(n): i, a, b = 0, 0, 1 while(i<n): yield b a, b = b, a+b i += 1 return ‘done‘ data = fib(5) print(data) print(data.__next__()) print(data.__next__()) print("我來打斷一下") print(data.__next__()) #結果如下 <generator object fib at 0x000002766915D6D0> 1 1 我來打斷一下 2
用for循環調用generator時,我們無法獲取generator的返回值,如果想要獲取到返回值,則要捕獲StopIteration異常,返回值包含在StopIteration的value中
data = fib(5) while True: try: x = next(data) print("data=",x) except StopIteration as e: print("generator return value:",e.value) break #程序結果是 data= 1 data= 1 data= 2 data= 3 data= 5 generator return value: done #這樣我們就可以獲取到這個返回值
二、裝飾器
接下來我們聊聊裝飾器,從字面意思來看是一個裝飾的效果,在java中也有裝飾模式,簡單來說裝飾器就是用來給函數添加新功能的,也就是函數功能的強化器,但是,裝飾器在裝飾函數的時候需要遵循幾個原則:
1、不能改變原函數的代碼
2、不能改變原函數的調用方式
3、不能改變原函數原有的結果
在聊裝飾器前我們簡單了解下高階函數和嵌套函數:
那什麽是高階函數呢?我們可以這麽理解,就是把一個函數a作為另一個函數b的參數進行傳遞,把函數當做變量來傳遞(我們可以理解函數即‘變量’),則函數b則是高階函數:
def func(x, y, f): print(f(x)+f(y)) func(-3,9,abs) #執行結果 12
那什麽是嵌套函數呢?嵌套函數並不是一個函數裏面調用另一個函數,而是在定義函數的時候在其裏面再定義函數的形式:
def func(x): def func1(y): print(y) func1(x) func(4) #執行結果是 4
好了,現在進去主題,聊聊我們的裝飾器,來看一段代碼(使用高階函數)
import time def bar(): #原函數,也就是要被裝飾的函數 time.sleep(1) print(‘in the bar‘) def test(f): start_time=time.time() f() stop_time=time.time() print("the f run time is %s" %(stop_time-start_time)) bar() print("分割線".center(50,‘-‘)) test(bar) #程序結果如下 in the bar -----------------------分割線------------------------ in the bar the f run time is 1.0021047592163086 Process finished with exit code 0
從上面結果看來,我們確實增強了函數bar的功能,但是不符合裝飾的一個原則,改變了調用方式,接下來我們處理這個問題。當然我們也可以使用bar=test(bar)的方式重新給了bar函數,也可以通過bar()的方式調用,也能達到了裝飾的效果,但是這樣我們沒法傳參數,接下來我們看一段代碼(嵌套函數出場了)
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) 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() test2("HZhuizai",22) #程序執行結果如下 in the test1 the func run time is 1.0001072883605957 test2: HZhuizai 22 the func run time is 0.0
是不是這個裝飾器很完美了,符合了三個原則,但是如果我們有一個需求就是對不同的兩個函數有不一樣的裝飾呢,那我們該怎麽實現呢?
import time user,passwd = ‘alex‘,‘abc123‘ def auth(auth_type): def outer_wrapper(func): def wrapper(*args, **kwargs): print("wrapper func args:", *args, **kwargs)
func() if auth_type == "local": print("local 模式驗證") elif auth_type == "ldap": print("ldap 模式驗證") return wrapper return outer_wrapper @auth(auth_type="local") # home = wrapper() def home(): print("welcome to home page") return "from home" @auth(auth_type="ldap") def bbs(): print("welcome to bbs page") home() #wrapper() print("分隔符".center(50,‘-‘)) bbs() #程序執行結果 wrapper func args:
welcome to home page
local 模式驗證
-----------------------分隔符------------------------
wrapper func args:
welcome to bbs page
ldap 模式驗證
為了能夠通過裝飾器傳遞參數,我們又在外層套了一個函數,可以歸納出裝飾就是高階函數和嵌套函數的組合,第一層為裝飾次傳遞參數,第二層是高階函數,把要裝飾的函數作為變量傳遞進去,第三層則是我們原函數的參數,這樣我們能完美的裝飾原函數,挺高函數的功能。
三、叠代器
我們知道可以作用於for循環的數據有列表、字典、元組、集合、字符串,這些都是集合類型,當然還有我們的生成器也可以作用於for循環,這些可以直接作用於for循環的對象統稱為叠代對象:Iterable,我們可以使用isinstance()判斷一個對象是否是Iterable對象。
>>> from collections import Iterable >>> isinstance([],Iterable) True >>> isinstance("",Iterable) True
可以被next()函數調用並且不斷返回下一個值的對象為叠代器:Iterator,同樣可以使用isinstance()判斷一個對象是否是Iterator對象。
>>> from collections import Iterator >>> isinstance((x for x in range(10)), Iterator) True >>> isinstance([x for x in range(10)], Iterator) False
列表、字典、元組、集合、字符串都是Iterable對象,我們可以通過iter()方法把Iterable對象轉換成Iterator對象
>>> isinstance(iter([]), Iterator) True >>> isinstance(iter(‘abc‘), Iterator) True
Iterator
對象表示的是一個數據流,Iterator對象可以被next()
函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration
錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()
函數實現按需計算下一個數據,所以Iterator
的計算是惰性的,只有在需要返回下一個數據時它才會計算。
Iterator
甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。
歸納下:
凡是可作用於for
循環的對象都是Iterable
類型;
凡是可作用於next()
函數的對象都是Iterator
類型,它們表示一個惰性計算的序列;
集合數據類型如list
、dict
、str
等是Iterable
但不是Iterator
,不過可以通過iter()
函數獲得一個Iterator
對象。
生成器、裝飾器、叠代器