【Python入門(mén)】9.高階函數(shù)之 閉包與裝飾器,裝飾器究竟是怎么運(yùn)行的?

筆記更新于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)?~*

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容