關(guān)于我
一個(gè)有思想的程序猿,終身學(xué)習(xí)實(shí)踐者肛度,目前在一個(gè)創(chuàng)業(yè)團(tuán)隊(duì)任team lead傻唾,技術(shù)棧涉及Android、Python承耿、Java和Go冠骄,這個(gè)也是我們團(tuán)隊(duì)的主要技術(shù)棧。
Github:https://github.com/hylinux1024
微信公眾號(hào):終身開發(fā)者(angrycode)
談?wù)勓b飾器(Decorator)的實(shí)現(xiàn)原理
熟悉Java
編程的程序猿對(duì)裝飾器模式一定不陌生加袋,它是能夠動(dòng)態(tài)的給一個(gè)類添加新的行為的一種設(shè)計(jì)模式凛辣。相對(duì)于通過繼承的方式使用裝飾器會(huì)更加靈活。
在Python
里面裝飾器(Decorator
)也是一個(gè)非常重要的概念职烧。跟裝飾器模式類似扁誓,它能夠動(dòng)態(tài)為一個(gè)函數(shù)防泵、方法或者類添加新的行為,而不需要通過子類繼承或直接修改函數(shù)的代碼來獲取新的行為能力蝗敢,使用Decorator
的方式會(huì)更加Pythonic
择克。
要理解裝飾器我們就要從函數(shù)說起。
0x00 函數(shù)
在Python
中函數(shù)是作為一級(jí)對(duì)象存在的(一切都是對(duì)象)前普,它擁有自己的屬性肚邢,函數(shù)名可以賦值給一個(gè)變量,也可以作為另一個(gè)函數(shù)的參數(shù)進(jìn)行傳遞拭卿。
1骡湖、定義函數(shù)
def fib(n):
"""打印小于 n 的 fibonacci 數(shù)列"""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
print()
def call_fib(func):
"""函數(shù)名作為函數(shù)的參數(shù)進(jìn)行傳遞"""
func(1000)
if __name__ == '__main__':
print(fib) # <function fib at 0x103e66378>
print(isinstance(fib, object)) # 函數(shù)是一級(jí)對(duì)象:True
print(fib.__name__) # 函數(shù)名稱:fib
print(fib.__code__.co_varnames) # __code__屬性是函數(shù)中的'代碼對(duì)象',co_varnames是函數(shù)中的本地變量峻厚,以元組的方式展現(xiàn):('n', 'a', 'b')
print(fib.__doc__) # 函數(shù)中的注釋
print(fib.__globals__) # 全局變量
f = fib # 函數(shù)賦值給一個(gè)變量f
f(1000) # 通過變量f對(duì)函數(shù)fib調(diào)用
call_fib(fib) # 函數(shù)名作為參數(shù)
2响蕴、嵌套函數(shù)
在定義函數(shù)時(shí),在函數(shù)內(nèi)部定義一個(gè)函數(shù)惠桃。
def outer_func():
# 在函數(shù)內(nèi)部定義一個(gè)函數(shù)
def inner_func():
print('inner func')
inner_func()
print('outer func')
嵌套函數(shù)對(duì)我們理解裝飾器非常重要浦夷,也是閉包實(shí)現(xiàn)的基礎(chǔ),這里先引出了本地變量和全局變量的概念辜王,后文會(huì)詳細(xì)說明閉包的概念劈狐。
3、全局變量(globals
)和本地變量(locals
)
根據(jù)作用域的不同呐馆,變量可以分成全局變量和本地變量肥缔,這其實(shí)是相對(duì)的概念。例如在下面的模塊中gvar
是一個(gè)全局變量汹来,而在函數(shù)outer_func()
定義的是本地變量续膳。
gvar = 'global var' # 全局變量
def outer_func():
gvar = 'outer var' # outer_func 本地變量
# 在函數(shù)內(nèi)部定義一個(gè)函數(shù)
def inner_func():
gvar = 'inner var' # inner_func 本地變量
print('inner: {}'.format(gvar))
inner_func()
print('outer: {}'.format(gvar))
outer_func()
print('gvar in global : {}'.format(gvar))
# 輸出結(jié)果
# inner: inner var
# outer: outer var
# gvar in global : global var
在函數(shù)外定義了全局變量gvar
,同時(shí)在outer_func()
函數(shù)中也定義了gvar
收班,而這個(gè)是本地變量坟岔。
從示例代碼中可以看到,outer_func()
并沒有改變?nèi)肿兞康闹怠?/p>
在函數(shù)中定義的變量都存儲(chǔ)在本地符號(hào)表(local symbol table
)里摔桦,同樣inner_func()
中的gvar
也存在它自己的本地符號(hào)表中社付,而全局變量gvar是則存儲(chǔ)在全局符號(hào)表(global symbol table
)。
變量的查找路是:首先從本地符號(hào)表中查找酣溃,然后是外部函數(shù)(在嵌套函數(shù)中)的本地符號(hào)表中查找瘦穆,再到全局符號(hào)表,最后是內(nèi)置符號(hào)表
graph TD
A[本地符號(hào)表]-->B[外部函數(shù)的本地符號(hào)表]
B[函數(shù)外的本地符號(hào)表]-->C[全局符號(hào)表]
C[全局符號(hào)表]-->D[內(nèi)置符號(hào)表]
如果把上面代碼中的inner_func()
中的gvar = 'inner var'
注釋掉赊豌,那么輸出的結(jié)果將是
# inner: outer gvar # inner_func中引用的gvar變量是outer_func中定義的
# outer: outer gvar
# gvar in global : global var
變量查找邏輯可以簡(jiǎn)單理解為:就近查找。
如果在以上路徑中都沒有找到绵咱,Python
解析器將拋出NameError: name 'gvar' is not defined
碘饼。
若在函數(shù)中要使用全局變量熙兔,那么就需要用到global
關(guān)鍵字。
對(duì)上文的代碼修改如下
gvar = 'global var'
def outer_func():
global gvar # 聲明gvar是全局變量
gvar = 'outer gvar'
# 在函數(shù)內(nèi)部定義一個(gè)函數(shù)
def inner_func():
gvar = 'inner gvar' # 這個(gè)依然是本地變量
print('inner: {}'.format(gvar))
inner_func()
print('outer: {}'.format(gvar))
outer_func()
print('gvar in global : {}'.format(gvar))
# 輸出結(jié)果
# inner: inner gvar
# outer: outer gvar
# gvar in global : outer gvar
除了global
還有一個(gè)nonlocal
的關(guān)鍵字艾恼,它的作用是在函數(shù)中使用外部函數(shù)的變量定義(注意:不能是全局變量)
gvar = 'global var' # 全局變量
def outer_func():
gvar = 'outer gvar' # 本地變量
# 在函數(shù)內(nèi)部定義一個(gè)函數(shù)
def inner_func():
nonlocal gvar # nonlocal的含義是讓gvar使用外部函數(shù)的變量住涉,
# 如果外部函數(shù)沒有定義該變量,那么運(yùn)行時(shí)將拋出SyntaxError: no binding for nonlocal 'gvar' found
gvar = 'inner gvar' # 這個(gè)依然是本地變量
print('inner: {}'.format(gvar))
inner_func()
print('outer: {}'.format(gvar))
# 輸出結(jié)果
# inner: inner gvar
# outer: inner gvar
# gvar in global : global var
在inner_func()
中使用nonlocal
關(guān)鍵字聲明的gvar
必須在外部函數(shù)(即outer_func()
函數(shù))定義钠绍,否則將拋出SyntaxError: no binding for nonlocal 'gvar' found
0x01 什么是閉包
首先結(jié)合前文的嵌套函數(shù)定義的例子舆声,修改一下代碼,返回內(nèi)部函數(shù)的對(duì)象柳爽。
# 普通的嵌套函數(shù)
def outer_func():
# 在函數(shù)內(nèi)部定義一個(gè)函數(shù)
def inner_func():
print('inner func')
inner_func()
print('outer func')
# 閉包
def closure_func():
local_var = 100
def inner_func():
print('inner func call : {}'.format(local_var))
return inner_func # 這里將形成一個(gè)閉包
f = closure_func()
print(f)
print(f.__closure__)
print(outer_func)
print(outer_func.__closure__)
# 輸出結(jié)果
# <function closure_func.<locals>.inner_func at 0x104f8a8c8>
# (<cell at 0x104f6fa68: int object at 0x104d23910>,)
# <function outer_func at 0x1070ea268>
# None # 普通函數(shù)的__closure__屬性為空
可以看出變量f
就是閉包媳握,它是一個(gè)函數(shù)對(duì)象,這個(gè)函數(shù)可以持有本地變量local_var
磷脯,而這個(gè)本地變量可以脫離定義它的作用域而存在蛾找。
現(xiàn)在來維基百科關(guān)于閉包的定義
在計(jì)算機(jī)科學(xué)中,閉包(英語:Closure)赵誓,又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function closures)打毛,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在俩功,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外幻枉。--引用自維基百科
0x02 實(shí)現(xiàn)裝飾器
有了前面的鋪墊,理解裝飾器就非常簡(jiǎn)單啦诡蜓。
一個(gè)函數(shù)返回另外一個(gè)函數(shù)展辞,通常會(huì)使用@wrapper
的語法形式,而裝飾器其實(shí)就是一種語法糖(syntactic sugar
)万牺。
我們還是看代碼
# 定義一個(gè)logger函數(shù)罗珍,在函數(shù)執(zhí)行前打印log信息
def logger(func):
def log_func(*args):
print('Running "{}" with arguments {}'.format(func.__name__, args))
return func(*args)
return log_func # 形成閉包
# 定義加法函數(shù)
def add(x, y):
return x + y
# 以下兩種方式的使用是等價(jià)的,當(dāng)然使用@logger更加Pythonic
@logger
def add(x, y):
return x + y
# add = logger(add)
print(add(1,4))
# 輸出結(jié)果
# Running "add" with arguments (1, 4)
# 5
這樣的通過自定義裝飾器脚粟,我們就可以動(dòng)態(tài)地給函數(shù)添加新的功能覆旱。
除了自定義的裝飾器,還有常見的如classmethod()
和staticmethod()
內(nèi)置的裝飾器核无。
0x03 總結(jié)
本文重點(diǎn)說明函數(shù)和嵌套函數(shù)的定義扣唱,還說明了全局變量和本地變量的作用域,在Python
中變量索引的路徑是就近查找团南,同時(shí)引出閉包是一個(gè)持有自由變量的函數(shù)對(duì)象的概念噪沙,而通過閉包可以實(shí)現(xiàn)裝飾器,在使用使用裝飾器時(shí)吐根,可以使用@wrapper
形式的語法糖正歼。