Python學習之路-Day4,python之路-day4
Python學習之路第4天
學習內容:
1.迭代器與產生器
2.裝飾器
1.迭代器與產生器
迭代器
迭代是Python最強大的功能之一,是訪問集合元素的一種方式。
迭代器是一個可以記住遍曆的位置的對象。
迭代器對象從集合等第一個元素開始訪問,直到所有的元素被訪問結束,迭代器只能往前不會後退。
迭代器有兩個基本的方法:iter()和next()
字串,列表或元組對象都可以用於建立迭代器。
迭代器的一大優點是不要求事先準備好整個迭代過程中所有的元素。迭代器僅僅在迭代到某個元素時才計算該元素,而在這之前或之後,元素可以不存在或者被銷毀。這個特點使得它特別適合用於遍曆一些巨大的或是無限的集合,比如幾個G的檔案
特點:
>>> list = [1,2,3,4]>>> it = iter(list) # 建立迭代器對象>>> print(next(it)) # 輸出迭代器的下一個元素1>>> print(next(it))2
迭代器對象可以使用常規for語句進行遍曆:
list=[1,2,3,4]it = iter(list) # 建立迭代器對象for x in it: print (x)
執行以上程式,輸出結果如下:
1234
也可以使用 next() 函數:
import sys # 引入 sys 模組list=[1,2,3,4]it = iter(list) # 建立迭代器對象while True: try: print (next(it)) except StopIteration: sys.exit()
執行以上程式,輸出結果如下:
1234
凡是能夠用for迴圈遍曆的資料結構和對象統統可以稱為可迭代的,即Iterable。我們現在接觸到Iterable資料結構有list、tuple、dict、string、set,還有產生器generator(下面將講到)。
但是,他們之間有什麼區別呢?generator不但可以使用for迴圈,還可以被next()函數不斷調用並返回下一個值,直到返回StopIteration錯誤。
能夠被next()函數不斷調用並返回下一個值的對象稱為迭代器Iterator。generator是Iterable,同時也是一個Iterator;而list、tuple、str、dict不是Iterator。
當然,可以通過isinstance()判斷一個對象是否為Iterator。
>>> from collections import Iterator>>> isinstance((x*x for x in range(10)), Iterator)True>>> isinstance([],Iterator)False>>> isinstance({},Iterator)False>>> isinstance("ABC",Iterator)False
如果想讓list、tuple、str、dict等Iterable對象轉變成Iterator,用iter()函數能夠讓Iterable對象變為Iterator
>>> isinstance(iter("ABC"),Iterator)True>>> i = iter("ABC")>>> next(i)'A'>>> next(i)'B'>>> next(i)'C'
Iterable和Iterator對象到底有什麼區別呢?
實際上,python中Iterator對象表示的是一個資料流,Iterator對象可以被next()函數不斷調用返回下一個值,直到資料全部被返回。Iterator對象資料流可以被看作是一個序列,但是這個序列的長度是不可提前預知的,因為只能通過next()或者for迴圈按照需要調用下一個資料,所以Iterator對象的計算是一種惰性計算。
正是因為這樣的特性,Iterator可以表示一個無限大的資料流,譬如全體自然數,而list是無法完成這樣的任務的。
列表產生式
在講到產生器之前我們先來講一下列表產生式
列表產生式,顧名思義,就是用來產生列表的。以前我們產生列表的時候,一般都是先定義一個list = [] ,再用for迴圈將元素一個一個append到list中。
簡單列表的產生,可以使用list(range(1,100)),那麼如果需要產生[1*1,2*2,3*3....]呢?
>>> [x*x for x in range(10)][0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
同時,還可以添加一些更多的要素,判斷使用符合要求的x參與計算。
>>> [x*x for x in range(10) if x%2==0][0, 4, 16, 36, 64]
使用多重迴圈還可以產生全排列
>>> [x+y for x in 'ABC' for y in 'LMN']['AL', 'AM', 'AN', 'BL', 'BM', 'BN', 'CL', 'CM', 'CN']>>>>>> [x+y+z for x in 'ABC' for y in 'LMN' for z in 'OPQ' if z=='Q']['ALQ', 'AMQ', 'ANQ', 'BLQ', 'BMQ', 'BNQ', 'CLQ', 'CMQ', 'CNQ']
產生器
通過清單產生器,我們可以創造一個列表。但是,受到記憶體容量的限制,列表的容量肯定是有限的,如若我們只需要使用列表中的一部分,就不需要一下子產生很大的列表,直接按需產生就好了,清單產生器就提供了這樣的功能。
產生器相當於是一個產生列表元素的演算法,有了這個演算法,就不用一下子產生很多元素了,只需要在需要的時候產生就可以了,節省了很多空間。在Python中,這種一邊迴圈一遍計算的機制,稱為:generator。
建立一個generator只需要把清單產生器的[]改成()就可以了。
>>> [x*x for x in range(10)][0, 1, 4, 9, 16, 25, 36, 49, 64, 81]>>> (x*x for x in range(10))<generator object <genexpr> at 0x00000259A50BD308>
擷取generator裡面的元素,可以使用next()函數產生generator的下一個元素。
>>> g=(x*x for x in range(10))>>> next(g)0>>> next(g)1>>> next(g)4...>>> next(g)81>>> next(g)Traceback (most recent call last): File "<stdin>", line 1, in <module>StopIteration
一直調用next()函數就可以遍曆generator的所有元素,直到拋出StopIteration錯誤。
但是最常用的當然還是for迴圈,畢竟generator也是Iterator的對象。
>>> g=(x*x for x in range(5))>>> for element in g:... print(element)...014916
一般我們建立一個generator後,基本上不會調用next(),而是通過for迴圈來迭代它,並且不需要關心StopIteration的錯誤。
有時候,推算元素的演算法很複雜,並不能通過清單產生器實現,而需要通過函數列印出來,譬如菲波那切數列。
#!/usr/bin/env pythondef fib(Max): n = 0 a,b = 0,1 while n < Max: print(b) a,b = b,a+b n = n + 1fib(5)結果:11235
在Python中,使用了yield的函數被稱為產生器(generator)。
跟普通函數不同的是,產生器是一個返回迭代器的函數,只能用於迭代操作,更簡單點理解產生器就是一個迭代器。
在調用產生器啟動並執行過程中,每次遇到yield時函數會暫停並儲存當前所有燈運行資訊,返回yield的值。並在下一次執行next()方法時從當前位置繼續運行。
#!/usr/bin/env pythondef fib(Max): n = 0 a,b = 0,1 while n < Max: yield b a,b = b,a+b n = n + 1g = fib(5)print(next(g))print(next(g))for i in g: print(i)結果:11235
在函數的執行過程中,遇到return才會返回,但是變成了generator的函數,遇到了yield就會中斷,並且返回yield的參數。
那麼,遇到了yield就會中斷並返回,我們能不能通過yield傳一些參數進去呢?
#!/usr/bin/env pythondef fib(Max): n = 0 a,b = 0,1 while n < Max: e = yield b print(e) a,b = b,a+b n = n + 1g = fib(5)print(next(g))print(next(g))print(g.send("the third element"))print(g.send("forth"))結果:1None1the third element2forth3
在上面的generator定義中,使用了e = yield b語句,這樣遇到yield返回b的同時,還能夠通過g.send(arg)傳入一個參數賦值給e,這樣,就可以通過generator進行參數傳遞了。
>>> def fib(Max):... n = 0... a,b = 0,1... while n < Max:... e = yield b... print(e)... a,b = b,a+b... n = n + 1...>>> g = fib(5)>>> g.send("the first element")Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: can't send non-None value to a just-started generator>>> g.send(None)1
注意,看上面的代碼,為什麼會報錯?說明啟動一個generator的時候,只能傳入None作為參數,而generator就相當於next(g)!
2.裝飾器
原則:
1、不能修改被裝飾函數的原始碼。
2、不能修改被裝飾函數的調用方式。
3、不能改變被裝飾函數的執行結果。
裝飾器對被裝飾函數是透明的。
如何理解裝飾器
1、函數即“變數”
2、高階函數
a:把一個函數名作為實參傳遞給另外一個函數
b:傳回值中包含函數名
3、嵌套函數
在一個函數體內聲明另一個函數稱為函數的嵌套
參考連結:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318435599930270c0381a3b44db991cd6d858064ac0000
裝飾器(decorator)是一種進階的Python文法。裝飾器可以對一個函數,方法或者類加工。
由於函數也是一個對象,而且函數對象可以被賦值給變數,所以,通過變數也能調用該函數。
>>> def now():... print('2017-05-01')...>>> f = now>>> f()2017-05-01
函數對象有一個__name__屬性,可以拿到函數的名字:
>>> now.__name__'now'>>> f.__name__'now'
現在,假設我們要增強now()函數的功能。比如,在函數調用前後自動列印日誌,但是又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,就稱之為:裝飾器(decorator)。
本質上,decorator就是一個返回函數的高階函數。所以,我們要定義一個能列印日誌的decorator,可以定義如下:
def log(func): def wrapper(*args, **kwargs): print("call %s():" % func.__name__) return func(*args, **kwargs) return wrapper
觀察上面的log,因為它是一個decorator,所以接受一個函數作為參數,並返回一個函數。我們要藉助Python的@文法,吧decorator置於函數的定義處:
@log # 把這句放在這兒就相當於執行了 now = log(now)def now(): print("2017-05-01")
調用now()函數,不僅會運行now()函數本身,還會運行now()函數前列印一行日誌:
>>> now()call now():2017-05-01
把@log放在now()函數的定義處,就相當於執行了語句:
now = log( now )
由於log()是一個decorator,返回一個函數,所以,原來的now()函數仍然存在,只是現在同名的now變數指向了新的函數,於是調用now()將執行新函數,即在log()函數中返回的wrapper()函數。
如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,寫出來會更複雜。比如,要自訂log的文本:
>>> def log(text):... def decorator(func):... def wrapper(*args, **kwargs):... print("%s %s():" % (text, func.__name__))... return func(*args, **kwargs)... return wrapper... return decorator...>>> @log("execute")... def now():... print("2017-05-01")...>>> now()execute now():2017-05-01
和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:
>>> now = log("execute")(now)
我們來剖析上面的語句,首先執行log('execute'),返回的是的decorator函數,在調用返回的函數,參數是now函數,傳回值最終是wrapper函數。
以上兩種decorator的定義都沒有問題,但還差最後一步。因為我們講了函數也是對象。它有__name__等屬性,但你去看經過decorator裝飾之後的函數,它們的__name__已經從原來的'now'變成了'wrapper':
>>> now.__name__'wrapper'
因為返回的那個wrapper()函數名字就是'wrapper',所以,需要把原始函數的__name__等屬性複製到wrapper()函數中,否則,有些依賴函數簽名的代碼執行就會出錯。
不需要編寫wrapper.__name__ = func.__name__這樣的代碼,Python內建的functools.wraps就是幹這個事的,所以,一個完整的decorator的寫法如下:
import functoolsdef log(func): @functools.wraps(func) def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper@log("execute")def now(): print("2017-05-01")now()
或者針對帶參數的decorator:
import functoolsdef log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print("%s %s():" % (text, func.__name__)) return func(*args, **kwargs) return wrapper return decorator@log("execute")def now(): print("2017-05-01")now()
裝飾器小結:
在物件導向(OOP)的設計模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實現,而Python除了能支援OOP的decorator外,直接從文法層次支援decorator(物件導向後面學到)。Python的decorator可以用函數實現,也可以用類實現。
decorator可以增強函數的功能,定義起來雖然有點複雜,但使用起來非常靈活和方便。