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

筆記更新于2019年11月19日硫豆,
摘要:返回函數(shù);閉包的介紹笼呆;裝飾器的工作原理解析


寫在前面:為了更好的學習python熊响,博主記錄下自己的學習路程。本學習筆記基于廖雪峰的Python教程诗赌,如有侵權汗茄,請告知刪除。歡迎與博主一起學習Pythonヽ( ̄▽ ̄)?


文章目錄

高階函數(shù)
返回函數(shù)
閉包
裝飾器
? 初步的裝飾器
? 完整裝飾器的構建
? 帶參數(shù)的裝飾器

高階函數(shù)

返回函數(shù)

高階函數(shù)除了可以把函數(shù)作為參數(shù)之外境肾,還能把函數(shù)作為返回值返回剔难。
如定義一個函數(shù),在函數(shù)內部再定義一個函數(shù)奥喻,而外面的函數(shù)把內部函數(shù)作為返回值偶宫。

def sum(*args):
    def sum1():
        a = 0
        for n in args:
            a = a + n
        return a
    return sum1

我們執(zhí)行sum( )這個函數(shù)

>>>f = sum(1, 2, 3)
>>>f
<function sum.<locals>.sum1 at 0x00000000021E37B8>

會發(fā)現(xiàn)返回的是一個函數(shù)sum1,而不是整數(shù)1环鲤,2纯趋,3的求和結果。我們需要調用函數(shù)f( )才能求出結果冷离。

>>>f()
6

需要注意的是吵冒,每次調用sum( ),都會返回一個新的函數(shù)西剥,即使傳入的參數(shù)是相同的痹栖。

>>>f1 = sum(1, 2, 3)
>>>f2 = sum(1, 2, 3)
>>>f1 == f2
False

閉包

在上面的例子中,我們稱sum為外部函數(shù)瞭空,sum1為內部函數(shù)揪阿,內部函數(shù)可以引用外部函數(shù)的參數(shù)和局部變量,而當外部函數(shù)返回內部函數(shù)時咆畏,參數(shù)和局部變量都保存在返回的函數(shù)中南捂,這種程序結構稱為閉包
需要注意的是旧找,返回的函數(shù)并沒有立刻執(zhí)行直到被調用溺健,因此在返回函數(shù)中最好不要引用任何循環(huán)變量或者后續(xù)會發(fā)生變化的變量,看個例子(以下例子轉自廖雪峰的官方網(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()

輸出結果

>>> f1()
9
>>> f2()
9
>>> f3()
9

結果不是認為中的1鞭缭,4,9愿卒,那是因為返回函數(shù)f引用的參數(shù)是i缚去,而等到函數(shù)執(zhí)行的時候,i等于3琼开,因此f1易结,f2,f3的結果都是9柜候「愣可以再定義一個函數(shù)來解決這個問題,但會略顯麻煩渣刷。

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的當前值被傳入f(),返回的是函數(shù)g
    return fs

輸出結果

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

裝飾器 Decorator

Decorator辅柴,在英文上是裝飾師的意思箩溃。顧名思義瞭吃,裝飾器的作用就是裝飾,在函數(shù)不需要做任何代碼變動的前提下增加額外功能涣旨,增加比如插入日志歪架、性能測試、權限校驗等場景霹陡。實質上和蚪,Decorator是一個高階函數(shù),它傳入一個函數(shù)烹棉,又返回一個函數(shù)攒霹,形成閉包的結構。下面將用一個簡單的例子解釋裝飾器的工作原理浆洗。
首先我們定義兩個簡單的函數(shù)催束。

def Ming():
    print('I am Ming.')
def Hong():
    print('I am Hong.')

在解釋之前插入一個小知識點,每個函數(shù)都有_name_屬性辅髓,可以拿到函數(shù)的名字泣崩,如:

>>>f = Ming
>>>Ming.__name__
Ming
>>>f.__name__                   #這里由于函數(shù)MIng賦給變量f,所f的__name__屬性也是Ming洛口。
Ming

再跟隨上面的例子矫付,我們希望在執(zhí)行Ming函數(shù)時還能打印出函數(shù)的執(zhí)行日志,于是可這樣添加語句第焰。

def Ming():
    print('Ming is running.')
    print('I am Ming.')

運行結果

>>>Ming()
Ming is running.
I am Ming.

那如果Hong也要這樣打印執(zhí)行日志呢买优,如果要每個函數(shù)都去修改的話就太麻煩了,于是我們可以先定義一個函數(shù)挺举,函數(shù)的參數(shù)是要執(zhí)行的函數(shù)杀赢,內容為先打印出執(zhí)行日志,再執(zhí)行需要的函數(shù)湘纵,像這樣:

def run(func):
    print('%s is running' % func.__name__)
    func()
    return
    
def Ming():
    print('I am Ming.')
    return
   

運行結果

>>>run(Ming)
Ming is running 
I am Ming. 
>>>run(Hong)
Hong is running 
I am Hong. 
? 初步的裝飾器

但是這樣的話脂崔,每次都要執(zhí)行run函數(shù),顯得麻煩而且我們本來是執(zhí)行Ming函數(shù)的梧喷,這樣就破壞了代碼的邏輯性砌左。如果用裝飾器的話就能簡單地解決這些問題了。下面是一個簡單的裝飾器铺敌。

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)

運行一下Ming函數(shù)汇歹。

>>>Ming()
Ming is runing. 
I am Ming. 

解釋一下上面裝飾器的運行過程。但我們執(zhí)行run(Ming)時偿凭,返回的是wrapper函數(shù)产弹,而根據(jù)wrapper函數(shù)的定義,它接收任意可變參數(shù)和關鍵字參數(shù)弯囊,然后執(zhí)行print痰哨,打印出執(zhí)行日志胶果,然后返回func與其對應的參數(shù),即返回Ming斤斧。所以最終的結果相當于打印出了執(zhí)行日志稽物,然后執(zhí)行Ming函數(shù)。

? 完整裝飾器的構建

再者折欠,我們可以借助Python中的@符號,來避免每次定義函數(shù)后都要進行函數(shù)賦值的操作吼过。在定義新的函數(shù)前加入@run即可锐秦,@run相當于執(zhí)行了Ming = run(Ming)語句,像這樣:

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. 

運行結果與上面一樣盗忱。
到目前為止酱床,裝飾器的建立差不多完成,但還有一個問題:在運行過程中趟佃,我們把原函數(shù)的信息給替換了扇谣,比如Ming的_name_屬性。

>>>Ming.__name__
wrapper

這是因為在執(zhí)行裝飾器run的時候闲昭,直接返回的函數(shù)是wrapper罐寨,那么要解決這個問題只需要將func的函數(shù)名賦給wrapper即可,這里Python內置了functools.wraps這個函數(shù)序矩,相當于執(zhí)行了wrapper._name_ = func._name_鸯绿。在裝飾器定義中添加這個@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.')

運行一下Ming.__name__

>>>Ming.__name__
Ming

問題解決簸淀。這樣瓶蝴,上面的一個decorator,裝飾器就是完整的裝飾器了租幕。

? 帶參數(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.')

運行結果

>>>Ming()
Ming is runing. Executed Successfully. 
I am Ming. 

以上就是本節(jié)的全部內容男窟,感謝你的閱讀。

下一節(jié)內容:10.模塊珠叔、包與作用域

有任何問題與想法蝎宇,歡迎評論與吐槽。

和博主一起學習Python吧( ̄▽ ̄)~*

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末祷安,一起剝皮案震驚了整個濱河市姥芥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汇鞭,老刑警劉巖凉唐,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庸追,死亡現(xiàn)場離奇詭異,居然都是意外死亡台囱,警方通過查閱死者的電腦和手機淡溯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來簿训,“玉大人咱娶,你說我怎么就攤上這事∏科罚” “怎么了膘侮?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長的榛。 經(jīng)常有香客問我琼了,道長,這世上最難降的妖魔是什么夫晌? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任雕薪,我火速辦了婚禮,結果婚禮上晓淀,老公的妹妹穿的比我還像新娘所袁。我一直安慰自己,他們只是感情好凶掰,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布纲熏。 她就那樣靜靜地躺著,像睡著了一般锄俄。 火紅的嫁衣襯著肌膚如雪局劲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天奶赠,我揣著相機與錄音鱼填,去河邊找鬼。 笑死毅戈,一個胖子當著我的面吹牛苹丸,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼居兆!你這毒婦竟也來了捞魁?” 一聲冷哼從身側響起位谋,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宦棺,沒想到半個月后沾鳄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體施流,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡响疚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞪醋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忿晕。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖银受,靈堂內的尸體忽然破棺而出践盼,到底是詐尸還是另有隱情,我是刑警寧澤宾巍,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布宏侍,位于F島的核電站,受9級特大地震影響蜀漆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜咱旱,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一确丢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吐限,春花似錦鲜侥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狐粱,卻和暖如春舀寓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肌蜻。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工互墓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蒋搜。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓篡撵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親豆挽。 傳聞我的和親對象是個殘疾皇子育谬,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內容