標籤:設計 markdown 來源 tor strong 變數 就會 直接 text
相關
函數範圍LEGB:L>E>G>B
- L:local函數內部範圍
- E:enclosing函數內部與內嵌函數之間(閉包)
- G:global全域範圍
- B:build-in內建範圍
閉包Closure概念:內建函式中隊enclosing範圍的變數進行引用,將該變數放到Closure的屬性中,當內建函式處理時,可以直接使用該變數。
函數實質與屬性
- 函數是一個對象
- 函數執行完成後內部變數回收
- 函數屬性
- 函數傳回值
閉包作用:1.封裝 2.代碼複用
裝飾器
裝飾器其實就是閉包所帶來的一個文法糖
裝飾器
由於函數也是一個對象,而且函數對象可以被賦值給變數,所以,通過變數也能調用該函數。
>>> from time import ctime>>> def now():... return ctime()... >>> f = now>>> f()'Sat Feb 24 16:43:28 2018'
函數對象有一個__name__
屬性,可以拿到函數的名字:
>>> now.__name__'now'>>> f.__name__'now'
現在,假設我們要增強now()
函數的功能,比如,在函數調用前後自動列印日誌,但又不希望修改now()
函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
本質上,decorator就是一個返回函數的高階函數。所以,我們要定義一個能列印日誌的decorator,可以定義如下:
>>> def log(func):... def wrapper(*args, **kw):... print('call %s():' % func.__name__)... return func(*args, **kw)... return wrapper
觀察上面的log
,因為它是一個decorator,所以接受一個函數作為參數,並返回一個函數。我們要藉助Python的@文法,把decorator置於函數的定義處:
@logdef now(): print '2013-12-25'
調用now()
函數,不僅會運行now()
函數本身,還會在運行now()
函數前列印一行日誌:
>>> @log... def now():... print(ctime())... ... >>> now()call now():Sat Feb 24 16:47:53 2018
把@log
放到now()
函數的定義處,相當於執行了語句:
now = log(now)
由於log()
是一個decorator,返回一個函數,所以,原來的now()
函數仍然存在,只是現在同名的now變數指向了新的函數,於是調用now()
將執行新函數,即在log()
函數中返回的wrapper()
函數。
wrapper()
函數的參數定義是(*args, **kw)
,因此,wrapper()
函數可以接受任意參數的調用。在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 '2013-12-25'
執行結果如下:
>>> now()execute now():2013-12-25
和兩層嵌套的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
或者針對帶參數的decorator:
import functoolsdef log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): print '%s %s():' % (text, func.__name__) return func(*args, **kw) return wrapper return decorator
import functools
是匯入functools
模組。模組的概念稍候講解。現在,只需記住在定義wrapper()
的前面加上@functools.wraps(func)
即可。
在物件導向(OOP)的設計模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實現,而Python除了能支援OOP的decorator外,直接從文法層次支援decorator。Python的decorator可以用函數實現,也可以用類實現。
decorator可以增強函數的功能,定義起來雖然有點複雜,但使用起來非常靈活和方便。
請編寫一個decorator,能在函數調用的前後列印出‘begin call‘
和‘end call‘
的日誌。
再思考一下能否寫出一個@log
的decorator,使它既支援:
@logdef f(): pass
又支援:
@log('execute')def f(): pass
偏函數
Python的functools
模組提供了很多有用的功能,其中一個就是偏函數(Partial function)。要注意,這裡的偏函數和數學意義上的偏函數不一樣。
在介紹函數參數的時候,我們講到,通過設定參數的預設值,可以降低函數調用的難度。而偏函數也可以做到這一點。舉例如下:
int()
函數可以把字串轉換為整數,當僅傳入字串時,int()
函數預設按十進位轉換:
>>> int('12345')12345
但int()
函數還提供額外的base
參數,預設值為10
。如果傳入base
參數,就可以做N進位的轉換:
>>> int('12345', base=8)5349>>> int('12345', 16)74565
假設要轉換大量的二進位字串,每次都傳入int(x, base=2)
非常麻煩,於是,我們想到,可以定義一個int2()
的函數,預設把base=2
傳進去:
def int2(x, base=2): return int(x, base)
這樣,我們轉換二進位就非常方便了:
>>> int2('1000000')64>>> int2('1010101')85
functools.partial
就是協助我們建立一個偏函數的,不需要我們自己定義int2()
,可以直接使用下面的代碼建立一個新的函數int2
:
>>> import functools>>> int2 = functools.partial(int, base=2)>>> int2('1000000')64>>> int2('1010101')85
所以,簡單總結functools.partial
的作用就是,把一個函數的某些參數給固定住(也就是設定預設值),返回一個新的函數,調用這個新函數會更簡單。
注意到上面的新的int2
函數,僅僅是把base
參數重新設定預設值為2
,但也可以在函數調用時傳入其他值:
>>> int2('1000000', base=10)1000000
最後,建立偏函數時,實際上可以接收函數對象、*args
和**kw
這3個參數,當傳入:
int2 = functools.partial(int, base=2)
實際上固定了int()函數的關鍵字參數base
,也就是:
int2('10010')
相當於:
kw = { base: 2 }int('10010', **kw)
當傳入:
max2 = functools.partial(max, 10)
實際上會把10
作為*args
的一部分自動加到左邊,也就是:
max2(5, 6, 7)
相當於:
args = (10, 5, 6, 7)max(*args)
結果為10
。
參考
來源於慕課網教程筆記
Python 裝飾器【轉】