筆記更新于2019年11月19日,
摘要:返回函數(shù);閉包的介紹;裝飾器的工作原理解析
寫(xiě)在前面:為了更好的學(xué)習(xí)python,博主記錄下自己的學(xué)習(xí)路程。本學(xué)習(xí)筆記基于廖雪峰的Python教程,如有侵權(quán),請(qǐng)告知?jiǎng)h除。歡迎與博主一起學(xué)習(xí)Pythonヽ( ̄▽?zhuān)??
文章目錄
高階函數(shù)
返回函數(shù)
閉包
裝飾器
? 初步的裝飾器
? 完整裝飾器的構(gòu)建
? 帶參數(shù)的裝飾器
高階函數(shù)
返回函數(shù)
高階函數(shù)除了可以把函數(shù)作為參數(shù)之外,還能把函數(shù)作為返回值返回。
如定義一個(gè)函數(shù),在函數(shù)內(nèi)部再定義一個(gè)函數(shù),而外面的函數(shù)把內(nèi)部函數(shù)作為返回值。
def sum(*args):
def sum1():
a = 0
for n in args:
a = a + n
return a
return sum1
我們執(zhí)行sum( )這個(gè)函數(shù)
>>>f = sum(1, 2, 3)
>>>f
<function sum.<locals>.sum1 at 0x00000000021E37B8>
會(huì)發(fā)現(xiàn)返回的是一個(gè)函數(shù)sum1,而不是整數(shù)1,2,3的求和結(jié)果。我們需要調(diào)用函數(shù)f( )才能求出結(jié)果。
>>>f()
6
需要注意的是,每次調(diào)用sum( ),都會(huì)返回一個(gè)新的函數(shù),即使傳入的參數(shù)是相同的。
>>>f1 = sum(1, 2, 3)
>>>f2 = sum(1, 2, 3)
>>>f1 == f2
False
閉包
在上面的例子中,我們稱(chēng)sum為外部函數(shù),sum1為內(nèi)部函數(shù),內(nèi)部函數(shù)可以引用外部函數(shù)的參數(shù)和局部變量,而當(dāng)外部函數(shù)返回內(nèi)部函數(shù)時(shí),參數(shù)和局部變量都保存在返回的函數(shù)中,這種程序結(jié)構(gòu)稱(chēng)為閉包。
需要注意的是,返回的函數(shù)并沒(méi)有立刻執(zhí)行直到被調(diào)用,因此在返回函數(shù)中最好不要引用任何循環(huán)變量或者后續(xù)會(huì)發(fā)生變化的變量,看個(gè)例子(以下例子轉(zhuǎn)自廖雪峰的官方網(wǎng)站)。
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
輸出結(jié)果
>>> f1()
9
>>> f2()
9
>>> f3()
9
結(jié)果不是認(rèn)為中的1,4,9,那是因?yàn)榉祷睾瘮?shù)f引用的參數(shù)是i,而等到函數(shù)執(zhí)行的時(shí)候,i等于3,因此f1,f2,f3的結(jié)果都是9??梢栽俣x一個(gè)函數(shù)來(lái)解決這個(gè)問(wèn)題,但會(huì)略顯麻煩。
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被執(zhí)行,因此i的當(dāng)前值被傳入f(),返回的是函數(shù)g
return fs
輸出結(jié)果
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
裝飾器 Decorator
Decorator,在英文上是裝飾師的意思。顧名思義,裝飾器的作用就是裝飾,在函數(shù)不需要做任何代碼變動(dòng)的前提下增加額外功能,增加比如插入日志、性能測(cè)試、權(quán)限校驗(yàn)等場(chǎng)景。實(shí)質(zhì)上,Decorator是一個(gè)高階函數(shù),它傳入一個(gè)函數(shù),又返回一個(gè)函數(shù),形成閉包的結(jié)構(gòu)。下面將用一個(gè)簡(jiǎn)單的例子解釋裝飾器的工作原理。
首先我們定義兩個(gè)簡(jiǎn)單的函數(shù)。
def Ming():
print('I am Ming.')
def Hong():
print('I am Hong.')
在解釋之前插入一個(gè)小知識(shí)點(diǎn),每個(gè)函數(shù)都有_name_屬性,可以拿到函數(shù)的名字,如:
>>>f = Ming
>>>Ming.__name__
Ming
>>>f.__name__ #這里由于函數(shù)MIng賦給變量f,所f的__name__屬性也是Ming。
Ming
再跟隨上面的例子,我們希望在執(zhí)行Ming函數(shù)時(shí)還能打印出函數(shù)的執(zhí)行日志,于是可這樣添加語(yǔ)句。
def Ming():
print('Ming is running.')
print('I am Ming.')
運(yùn)行結(jié)果
>>>Ming()
Ming is running.
I am Ming.
那如果Hong也要這樣打印執(zhí)行日志呢,如果要每個(gè)函數(shù)都去修改的話就太麻煩了,于是我們可以先定義一個(gè)函數(shù),函數(shù)的參數(shù)是要執(zhí)行的函數(shù),內(nèi)容為先打印出執(zhí)行日志,再執(zhí)行需要的函數(shù),像這樣:
def run(func):
print('%s is running' % func.__name__)
func()
return
def Ming():
print('I am Ming.')
return
運(yùn)行結(jié)果
>>>run(Ming)
Ming is running
I am Ming.
>>>run(Hong)
Hong is running
I am Hong.
? 初步的裝飾器
但是這樣的話,每次都要執(zhí)行run函數(shù),顯得麻煩而且我們本來(lái)是執(zhí)行Ming函數(shù)的,這樣就破壞了代碼的邏輯性。如果用裝飾器的話就能簡(jiǎn)單地解決這些問(wèn)題了。下面是一個(gè)簡(jiǎn)單的裝飾器。
def run(func):
def wrapper(*args, **kw): #wrapper在英文中是包裝紙的意思
print('%s is runing.' % func.__name__)
return func(*args, **kw)
return wrapper
def Ming():
print('I am Ming.')
Ming = run(Ming)
運(yùn)行一下Ming函數(shù)。
>>>Ming()
Ming is runing.
I am Ming.
解釋一下上面裝飾器的運(yùn)行過(guò)程。但我們執(zhí)行run(Ming)時(shí),返回的是wrapper函數(shù),而根據(jù)wrapper函數(shù)的定義,它接收任意可變參數(shù)和關(guān)鍵字參數(shù),然后執(zhí)行print,打印出執(zhí)行日志,然后返回func與其對(duì)應(yīng)的參數(shù),即返回Ming。所以最終的結(jié)果相當(dāng)于打印出了執(zhí)行日志,然后執(zhí)行Ming函數(shù)。
? 完整裝飾器的構(gòu)建
再者,我們可以借助Python中的@符號(hào),來(lái)避免每次定義函數(shù)后都要進(jìn)行函數(shù)賦值的操作。在定義新的函數(shù)前加入@run即可,@run相當(dāng)于執(zhí)行了Ming = run(Ming)語(yǔ)句,像這樣:
def run(func):
def wrapper(*args, **kw):
print('%s is runing.' % func.__name__)
return func(*args, **kw)
return wrapper
@run
def Ming():
print('I am Ming.')
>>>Ming()
Ming is runing.
I am Ming.
運(yùn)行結(jié)果與上面一樣。
到目前為止,裝飾器的建立差不多完成,但還有一個(gè)問(wèn)題:在運(yùn)行過(guò)程中,我們把原函數(shù)的信息給替換了,比如Ming的_name_屬性。
>>>Ming.__name__
wrapper
這是因?yàn)樵趫?zhí)行裝飾器run的時(shí)候,直接返回的函數(shù)是wrapper,那么要解決這個(gè)問(wèn)題只需要將func的函數(shù)名賦給wrapper即可,這里Python內(nèi)置了functools.wraps這個(gè)函數(shù),相當(dāng)于執(zhí)行了wrapper._name_ = func._name_。在裝飾器定義中添加這個(gè)@functools.wraps( ),像這樣:
import functools
def run(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s is runing.' % func.__name__)
return func(*args, **kw)
return wrapper
@run
def Ming():
print('I am Ming.')
運(yùn)行一下Ming.__name__
>>>Ming.__name__
Ming
問(wèn)題解決。這樣,上面的一個(gè)decorator,裝飾器就是完整的裝飾器了。
? 帶參數(shù)的裝飾器
當(dāng)然我們還可以給裝飾器加入更大的靈活性,如定義一個(gè)帶參數(shù)的裝飾器,這時(shí)就需要用三層嵌套的高階函數(shù)了,如下
import functools
def 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
@run('Executed Successfully.')
def Ming():
print('I am Ming.')
運(yùn)行結(jié)果
>>>Ming()
Ming is runing. Executed Successfully.
I am Ming.
以上就是本節(jié)的全部?jī)?nèi)容,感謝你的閱讀。
下一節(jié)內(nèi)容:10.模塊、包與作用域
有任何問(wèn)題與想法,歡迎評(píng)論與吐槽。
和博主一起學(xué)習(xí)Python吧( ̄▽?zhuān)?~*