無論項目中還是面試都離不開裝飾器話題糊昙,裝飾器的強大在于它能夠在不修改原有業(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件事:
- 給 變量
x
賦值為1 - 定義嵌套函數(shù)
inner
伞鲫,此時并不會執(zhí)行 inner 中的代碼粘茄,因為該函數(shù)還沒被調(diào)用,直到第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)用場景观游。