Python裝飾器為什么難理解沦泌?

無論項目中還是面試都離不開裝飾器話題糊昙,裝飾器的強大在于它能夠在不修改原有業(yè)務(wù)邏輯的情況下對代碼進(jìn)行擴(kuò)展,權(quán)限校驗谢谦、用戶認(rèn)證释牺、日志記錄、性能測試回挽、事務(wù)處理没咙、緩存等都是裝飾器的絕佳應(yīng)用場景,它能夠最大程度地對代碼進(jìn)行復(fù)用千劈。

但為什么初學(xué)者對裝飾器的理解如此困難祭刚,我認(rèn)為本質(zhì)上是對Python函數(shù)理解不到位,因為裝飾器本質(zhì)上還是函數(shù)

函數(shù)定義

理解裝飾器前墙牌,需要明白函數(shù)的工作原理涡驮,我們先從一個最簡單函數(shù)定義開始:

def foo(num):
    return num + 1

上面定義了一個函數(shù),名字叫foo喜滨,也可以把 foo 可理解為變量名捉捅,該變量指向一個函數(shù)對象

調(diào)用函數(shù)只需要給函數(shù)名加上括號并傳遞必要的參數(shù)(如果函數(shù)定義的時候有參數(shù)的話)

value = foo(3)
print(value) # 4

變量名 foo 現(xiàn)在指向 <function foo at 0x1030060c8> 函數(shù)對象,但它也可以指向另外一個函數(shù)虽风。

def bar():
    print("bar")
foo = bar
foo() # bar

函數(shù)作為返回值

在Python中棒口,一切皆為對象寄月,函數(shù)也不例外,它可以像整數(shù)一樣作為其它函數(shù)的返回值无牵,例如:

def foo():
    return 1

def bar():
    return foo

print(bar()) # <function foo at 0x10a2f4140>

print(bar()()) # 1 
# 等價于
print(foo()) # 1


調(diào)用函數(shù) bar() 的返回值是一個函數(shù)對象 <function foo at 0x10a2f4140>漾肮,因為返回值是函數(shù),所以我們可以繼續(xù)對返回值進(jìn)行調(diào)用(記拙セ佟:調(diào)用函數(shù)就是在函數(shù)名后面加())調(diào)用bar()()相當(dāng)于調(diào)用 foo()克懊,因為 變量 foo 指向的對象與 bar() 的返回值是同一個對象。

函數(shù)作為參數(shù)

函數(shù)還可以像整數(shù)一樣作為函數(shù)的參數(shù)充岛,例如:

def foo(num):
    return num + 1

def bar(fun):
    return fun(3)

value = bar(foo)
print(value)  # 4

函數(shù) bar 接收一個參數(shù)保檐,這個參數(shù)是一個可被調(diào)用的函數(shù)對象,把函數(shù) foo 傳遞到 bar 中去時崔梗,foo 和 fun 兩個變量名指向的都是同一個函數(shù)對象夜只,所以調(diào)用 fun(3) 相當(dāng)于調(diào)用 foo(3)。

函數(shù)嵌套

函數(shù)不僅可以作為參數(shù)和返回值蒜魄,函數(shù)還可以定義在另一個函數(shù)中扔亥,作為嵌套函數(shù)存在,例如:

def outer():
    x = 1
    def inner():
        print(x)
    inner()

outer() # 1

inner做為嵌套函數(shù)谈为,它可以訪問外部函數(shù)的變量旅挤,調(diào)用 outer 函數(shù)時,發(fā)生了3件事:

  1. 給 變量 x 賦值為1
  2. 定義嵌套函數(shù) inner伞鲫,此時并不會執(zhí)行 inner 中的代碼粘茄,因為該函數(shù)還沒被調(diào)用,直到第3步
  3. 調(diào)用 inner 函數(shù)秕脓,執(zhí)行 inner 中的代碼邏輯柒瓣。

閉包

再來看一個例子:

def outer(x):
    def inner():
        print(x)

    return inner
closure = outer(1)
closure() # 1

同樣是嵌套函數(shù),只是稍改動一下吠架,把局部變量 x 作為參數(shù)了傳遞進(jìn)來芙贫,嵌套函數(shù)不再直接在函數(shù)里被調(diào)用,而是作為返回值返回傍药,這里的 closure就是一個閉包磺平,本質(zhì)上它還是函數(shù),閉包是引用了自由變量(x)的函數(shù)(inner)拐辽。

裝飾器

繼續(xù)往下看:

def foo():
    print("foo")

上面這個函數(shù)這可能是史上最簡單的業(yè)務(wù)代碼了拣挪,雖然沒什么用,但是能說明問題就行【阒睿現(xiàn)在菠劝,有一個新的需求,需要在執(zhí)行該函數(shù)時加上日志:

def foo():
    print("記錄日志開始")
    print("foo")
    print("記錄日志結(jié)束")

功能實現(xiàn)乙埃,唯一的問題就是它需要侵入到原來的代碼里面闸英,把日志邏輯加上去,如果還有好幾十個這樣的函數(shù)要加日志介袜,也必須這樣做甫何,顯然,這樣的代碼一點都不Pythonic遇伞。那么有沒有可能在不修改業(yè)務(wù)代碼的提前下辙喂,實現(xiàn)日志功能呢?答案就是裝飾器鸠珠。

def outer(func):
    def inner():
        print("記錄日志開始")
        func() # 業(yè)務(wù)函數(shù)
        print("記錄日志結(jié)束")
    return inner

def foo():
    print("foo")

foo = outer(foo) 
foo()

我沒有修改 foo 函數(shù)里面的任何邏輯巍耗,只是給 foo 變量重新賦值了,指向了一個新的函數(shù)對象渐排。最后調(diào)用 foo()炬太,不僅能打印日志,業(yè)務(wù)邏輯也執(zhí)行完了⊙背埽現(xiàn)在來分析一下它的執(zhí)行流程亲族。

這里的 outer 函數(shù)其實就是一個裝飾器,裝飾器是一個帶有函數(shù)作為參數(shù)并返回一個新函數(shù)的閉包可缚,本質(zhì)上裝飾器也是函數(shù)霎迫。outer 函數(shù)的返回值是 inner 函數(shù),在 inner 函數(shù)中帘靡,除了執(zhí)行日志操作知给,還有業(yè)務(wù)代碼,該函數(shù)重新賦值給 foo 變量后描姚,調(diào)用 foo() 就相當(dāng)于調(diào)用 inner()

foo 重新賦值前:

重新賦值后涩赢,foo = outer(foo)

另外,Python為裝飾器提供了語法糖 @轰胁,它用在函數(shù)的定義處:

@outer
def foo():
    print("foo")

foo()

這樣就省去了手動給foo重新賦值的步驟谒主。

到這里不知你對裝飾器理解了沒有?當(dāng)然赃阀,裝飾器還可以更加復(fù)雜霎肯,比如可以接受參數(shù)的裝飾器,基于類的裝飾器等等榛斯。下一篇可以寫寫裝飾器的應(yīng)用場景观游。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市驮俗,隨后出現(xiàn)的幾起案子懂缕,更是在濱河造成了極大的恐慌,老刑警劉巖王凑,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搪柑,死亡現(xiàn)場離奇詭異聋丝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)工碾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門弱睦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人渊额,你說我怎么就攤上這事况木。” “怎么了旬迹?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵火惊,是天一觀的道長。 經(jīng)常有香客問我奔垦,道長屹耐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任椿猎,我火速辦了婚禮张症,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鸵贬。我一直安慰自己俗他,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布阔逼。 她就那樣靜靜地躺著兆衅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嗜浮。 梳的紋絲不亂的頭發(fā)上羡亩,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音危融,去河邊找鬼畏铆。 笑死,一個胖子當(dāng)著我的面吹牛吉殃,可吹牛的內(nèi)容都是我干的辞居。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛋勺,長吁一口氣:“原來是場噩夢啊……” “哼瓦灶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抱完,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤贼陶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碉怔,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡烘贴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了撮胧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庙楚。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖趴樱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酪捡,我是刑警寧澤叁征,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站逛薇,受9級特大地震影響捺疼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜永罚,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一啤呼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呢袱,春花似錦官扣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至治专,卻和暖如春卖陵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背张峰。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工泪蔫, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喘批。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓撩荣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饶深。 傳聞我的和親對象是個殘疾皇子婿滓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 呵呵!作為一名教python的老師粥喜,我發(fā)現(xiàn)學(xué)生們基本上一開始很難搞定python的裝飾器凸主,也許因為裝飾器確實很難懂...
    TypingQuietly閱讀 19,551評論 26 186
  • Python的裝飾器的英文名叫Decorator,要對一個已有的模塊做一些“修飾工作”额湘,所謂修飾工作就是想給現(xiàn)有的...
    Spareribs閱讀 676評論 1 11
  • 原文出處: dzone 譯文出處:Wu Cheng(@nullRef) 1. 函數(shù) 在python中卿吐,函數(shù)通過...
    DraculaWong閱讀 521評論 0 3
  • 本文為《爬著學(xué)Python》系列第四篇文章旁舰。從本篇開始,本專欄在順序更新的基礎(chǔ)上嗡官,會有不規(guī)則的更新箭窜。 在Pytho...
    SyPy閱讀 2,501評論 4 11
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)衍腥,斷路器磺樱,智...
    卡卡羅2017閱讀 134,654評論 18 139