學習內容:python
1.裝飾器算法
2.迭代器設計模式
3.生成器數據結構
1.裝飾器 app
原則:
一、不能修改被裝飾函數的源代碼。
二、不能修改被裝飾函數的調用方式。
三、不能改變被裝飾函數的執行結果。
裝飾器對被裝飾函數是透明的。函數
如何理解裝飾器
一、函數即「變量」
二、高階函數
a:把一個函數名做爲實參傳遞給另一個函數(在不修改被裝飾函數的狀況下爲其添加功能)
b:返回值中包含函數名(不修改函數的調用方式)
三、嵌套函數
在一個函數體內聲明另外一個函數稱爲函數的嵌套學習
裝飾器(decorator)是一種高級的Python語法。裝飾器能夠對一個函數,方法或者類加工。spa
因爲函數也是一個對象,並且函數對象能夠被賦值給變量,因此,經過變量也能調用該函數。設計
>>> def now(): print('2018-03-24') >>> f = now() 2018-03-24 >>> f = now >>> f() 2018-03-24 >>>
函數對象有一個__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("2018-03-24")
調用now()函數,不只會運行now()函數自己,還會運行now()函數前打印一行日誌:
>>> now() call now(): 2018-03-24
把@log放在now()函數的定義處,就至關於執行了語句:
now = log( now )
因爲log()是一個decorator,返回一個函數,因此,原來的now()函數仍然存在,只是如今同名的now變量指向了新的函數,因而調用now()將執行新函數,即在log()函數中返回的wrapper()函數。
若是decorator自己須要傳入參數,那就須要編寫一個返回decorator的高階函數,寫出來會更復雜。好比,要自定義log的文本:
def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator
這個3層嵌套的decorator用法以下:
@log('execute') def now(): print('2018-03-24')
執行結果以下:
>>> now() execute now(): 2018-03-24
和兩層嵌套的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 functools def 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("2018-03-24") now()
或者針對帶參數的decorator:
import functools def 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("2018-03-34") now()
import functools
是導入functools
模塊。模塊的概念稍候講解。如今,只需記住在定義wrapper()
的前面加上@functools.wraps(func)
便可。
請設計一個decorator,它可做用於任何函數上,並打印該函數的執行時間:
#!/user/bin/env python # -*- coding: UTF-8 -*- # Author: cs import time, functools def metric(fn): print('%s executed in %s ms' % (fn.__name__, 10.24)) return fn # 測試 @metric def fast(x, y): time.sleep(0.0012) return x + y; @metric def slow(x, y, z): time.sleep(0.1234) return x * y * z; f = fast(11, 22) s = slow(11, 22, 33) if f != 33: print('測試失敗!') elif s != 7986: print('測試失敗!')
在面向對象(OOP)的設計模式中,decorator被稱爲裝飾模式。OOP的裝飾模式須要經過繼承和組合來實現,而Python除了能支持OOP的decorator外,直接從語法層次支持decorator。Python的decorator能夠用函數實現,也能夠用類實現。
2.迭代器
迭代是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)
執行以上程序,輸出結果以下:
1 2 3 4
也可使用 next() 函數:
複製代碼 import sys # 引入 sys 模塊 list=[1,2,3,4] it = iter(list) # 建立迭代器對象 while True: try: print (next(it)) except StopIteration: sys.exit()
執行以上程序,輸出結果以下:
1 2 3 4
凡是可以用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'
列表生成式
在講到生成器以前咱們先來說一下列表生成式
列表生成式,顧名思義,就是用來生成列表的。之前咱們生成列表的時候,通常都是先定義一個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']
3.生成器
經過列表生成器,咱們能夠創造一個列表。可是,受到內存容量的限制,列表的容量確定是有限的,如若咱們只須要使用列表中的一部分,就不須要一會兒生成很大的列表,直接按需生成就行了,列表生成器就提供了這樣的功能。
生成器至關因而一個生成列表元素的算法,有了這個算法,就不用一會兒生成不少元素了,只須要在須要的時候生成就能夠了,節省了不少空間。在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)
...
0
1
4
9
16
通常咱們建立一個generator後,基本上不會調用next(),而是經過for循環來迭代它,而且不須要關心StopIteration的錯誤。
有時候,推算元素的算法很複雜,並不能經過列表生成器實現,而須要經過函數打印出來,譬如菲波那切數列。
def fib(Max): n = 0 a,b = 0,1 while n < Max: print(b) a,b = b,a+b n = n + 1 fib(5) 結果: 1 1 2 3 5
在Python中,使用了yield的函數被稱爲生成器(generator)。
跟普通函數不一樣的是,生成器是一個返回迭代器的函數,只能用於迭代操做,更簡單點理解生成器就是一個迭代器。
在調用生成器運行的過程當中,每次遇到yield時函數會暫停並保存當前全部燈運行信息,返回yield的值。並在下一次執行next()方法時從當前位置繼續運行。
#!/usr/bin/env python def fib(Max): n = 0 a,b = 0,1 while n < Max: yield b a,b = b,a+b n = n + 1 g = fib(5) print(next(g)) print(next(g)) for i in g: print(i) 結果: 1 1 2 3 5
在函數的執行過程當中,遇到return纔會返回,可是變成了generator的函數,遇到了yield就會中斷,而且返回yield的參數。
那麼,遇到了yield就會中斷並返回,咱們能不能經過yield傳一些參數進去呢?
#!/usr/bin/env python 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) print(next(g)) print(next(g)) print(g.send("the third element")) print(g.send("forth")) 結果: 1 None 1 the third element 2 forth 3
在上面的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)!