Python裝飾器為什么難理解?

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

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

函數(shù)定義

理解裝飾器前,需要明白函數(shù)的工作原理,我們先從一個(gè)最簡(jiǎn)單函數(shù)定義開(kāi)始:

def foo(num):
    return num + 1

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

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

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

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

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

函數(shù)作為返回值

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

def foo():
    return 1

def bar():
    return foo

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

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


調(diào)用函數(shù) bar() 的返回值是一個(gè)函數(shù)對(duì)象 <function foo at 0x10a2f4140>,因?yàn)榉祷刂凳呛瘮?shù),所以我們可以繼續(xù)對(duì)返回值進(jìn)行調(diào)用(記?。赫{(diào)用函數(shù)就是在函數(shù)名后面加())調(diào)用bar()()相當(dāng)于調(diào)用 foo(),因?yàn)?變量 foo 指向的對(duì)象與 bar() 的返回值是同一個(gè)對(duì)象。

函數(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 接收一個(gè)參數(shù),這個(gè)參數(shù)是一個(gè)可被調(diào)用的函數(shù)對(duì)象,把函數(shù) foo 傳遞到 bar 中去時(shí),foo 和 fun 兩個(gè)變量名指向的都是同一個(gè)函數(shù)對(duì)象,所以調(diào)用 fun(3) 相當(dāng)于調(diào)用 foo(3)。

函數(shù)嵌套

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

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

outer() # 1

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

  1. 給 變量 x 賦值為1
  2. 定義嵌套函數(shù) inner,此時(shí)并不會(huì)執(zhí)行 inner 中的代碼,因?yàn)樵摵瘮?shù)還沒(méi)被調(diào)用,直到第3步
  3. 調(diào)用 inner 函數(shù),執(zhí)行 inner 中的代碼邏輯。

閉包

再來(lái)看一個(gè)例子:

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

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

同樣是嵌套函數(shù),只是稍改動(dòng)一下,把局部變量 x 作為參數(shù)了傳遞進(jìn)來(lái),嵌套函數(shù)不再直接在函數(shù)里被調(diào)用,而是作為返回值返回,這里的 closure就是一個(gè)閉包,本質(zhì)上它還是函數(shù),閉包是引用了自由變量(x)的函數(shù)(inner)。

裝飾器

繼續(xù)往下看:

def foo():
    print("foo")

上面這個(gè)函數(shù)這可能是史上最簡(jiǎn)單的業(yè)務(wù)代碼了,雖然沒(méi)什么用,但是能說(shuō)明問(wèn)題就行?,F(xiàn)在,有一個(gè)新的需求,需要在執(zhí)行該函數(shù)時(shí)加上日志:

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

功能實(shí)現(xiàn),唯一的問(wèn)題就是它需要侵入到原來(lái)的代碼里面,把日志邏輯加上去,如果還有好幾十個(gè)這樣的函數(shù)要加日志,也必須這樣做,顯然,這樣的代碼一點(diǎn)都不Pythonic。那么有沒(méi)有可能在不修改業(yè)務(wù)代碼的提前下,實(shí)現(xiàn)日志功能呢?答案就是裝飾器。

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

def foo():
    print("foo")

foo = outer(foo) 
foo()

我沒(méi)有修改 foo 函數(shù)里面的任何邏輯,只是給 foo 變量重新賦值了,指向了一個(gè)新的函數(shù)對(duì)象。最后調(diào)用 foo(),不僅能打印日志,業(yè)務(wù)邏輯也執(zhí)行完了?,F(xiàn)在來(lái)分析一下它的執(zhí)行流程。

這里的 outer 函數(shù)其實(shí)就是一個(gè)裝飾器,裝飾器是一個(gè)帶有函數(shù)作為參數(shù)并返回一個(gè)新函數(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為裝飾器提供了語(yǔ)法糖 @,它用在函數(shù)的定義處:

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

foo()

這樣就省去了手動(dòng)給foo重新賦值的步驟。

到這里不知你對(duì)裝飾器理解了沒(méi)有?當(dāng)然,裝飾器還可以更加復(fù)雜,比如可以接受參數(shù)的裝飾器,基于類的裝飾器等等。下一篇可以寫寫裝飾器的應(yīng)用場(chǎng)景。

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 呵呵!作為一名教python的老師,我發(fā)現(xiàn)學(xué)生們基本上一開(kāi)始很難搞定python的裝飾器,也許因?yàn)檠b飾器確實(shí)很難懂...
    TypingQuietly閱讀 20,325評(píng)論 26 186
  • Python的裝飾器的英文名叫Decorator,要對(duì)一個(gè)已有的模塊做一些“修飾工作”,所謂修飾工作就是想給現(xiàn)有的...
    Spareribs閱讀 749評(píng)論 1 11
  • 原文出處: dzone 譯文出處:Wu Cheng(@nullRef) 1. 函數(shù) 在python中,函數(shù)通過(guò)...
    DraculaWong閱讀 589評(píng)論 0 3
  • 本文為《爬著學(xué)Python》系列第四篇文章。從本篇開(kāi)始,本專欄在順序更新的基礎(chǔ)上,會(huì)有不規(guī)則的更新。 在Pytho...
    SyPy閱讀 2,584評(píng)論 4 11
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,715評(píng)論 19 139

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