一晃择、裝飾器
裝飾器背后的主要動機(jī)源自 python 面向?qū)ο缶幊獭Qb飾器是在函數(shù)調(diào)用之上的修飾罪佳。這些修飾僅是當(dāng)聲明一個(gè)函數(shù)或者方法的時(shí)候却舀,才會應(yīng)用的額外調(diào)用虫几。
裝飾器的語法以 @
開頭,接著是裝飾器函數(shù)的名字和可選的參數(shù)挽拔。緊跟著裝飾器聲明的是被修飾的函數(shù)辆脸,和裝飾函數(shù)的可選參數(shù)。
有參數(shù)和無參數(shù)的裝飾器
沒有參數(shù)的裝飾器:
帶參數(shù)的裝飾器 :
現(xiàn)在我們知道裝飾器實(shí)際就是函數(shù)术裸。我們也知道他們接受函數(shù)對象倘是。一般說來,當(dāng)你包裝一個(gè)函數(shù)的時(shí)候袭艺,你可以在包裝的環(huán)境下在合適的時(shí)機(jī)調(diào)用這個(gè)函數(shù)辨绊。我們在執(zhí)行函數(shù)之前,可以運(yùn)行些預(yù)備代碼匹表,如 post-morrem 分析,也可以在執(zhí)行代碼之后做些清理工作宣鄙。
你可以考慮在裝飾器中置入通用功能的代碼來降低程序復(fù)雜度袍镀。例如,可以用裝飾器來:
- 引入日志
- 增加計(jì)時(shí)邏輯來檢測性能
- 給函數(shù)加入事務(wù)的能力
對于用 python 創(chuàng)建企業(yè)級應(yīng)用冻晤,支持裝飾器的特性是非常重要的苇羡。
二、裝飾器的原理探析
我們可以跟著下面的例子更深層次的理解裝飾器的原理鼻弧。
當(dāng)你要在一個(gè)函數(shù)A之前或者之后增加一些操作的話设江,我們可以定義一個(gè)新的函數(shù)B,在函數(shù)B中定義這些操作攘轩,并在指定的位置調(diào)用函數(shù)A叉存。這樣我們就等到了一個(gè)新的函數(shù)對象,他封裝的對函數(shù)A執(zhí)行的額外的操作度帮。并且可以把B函數(shù)名賦值給其他變量歼捏。那怎樣將func和B綁定到一個(gè)作用域呢脯宿?讓他們綁定到一起成為一個(gè)新的函數(shù)對象返回。要知道上面的例子中b和b2都是指向了統(tǒng)一個(gè)對象B泉粉,所以他們的調(diào)用結(jié)果才都是一樣的连霉。
這時(shí)我們可以利用嵌套函數(shù)的一個(gè)特點(diǎn),就是 在嵌套函數(shù)中嗡靡,將內(nèi)層函數(shù)對象作為返回值返回的話跺撼,內(nèi)層函數(shù)對象可以保留其所在的作用域(外層函數(shù)的作用域),也就是外層函數(shù)中定義的一切變量都可以跟隨內(nèi)層函數(shù)得到保留讨彼。 利用這個(gè)特性歉井,因?yàn)樾螀⒁蔡幱诤瘮?shù)作用域中,所以我們讓外層函數(shù)來接受參數(shù)哈误,當(dāng)外層函數(shù)調(diào)用結(jié)束后哩至,返回的內(nèi)層函數(shù)還處于一個(gè)封閉的作用域中,并可以使用外層函數(shù)的參數(shù)蜜自。
按照上面的思路菩貌,我們可以在函數(shù)B的外層再封裝一層函數(shù)C,讓外層的函數(shù)C來接受參數(shù)重荠,而函數(shù)B不用自己傳入?yún)?shù)箭阶,直接使用外層的函數(shù)C就可以了。這樣我們可以在外層的函數(shù)C中直接將使用了外層函數(shù)C參數(shù)的函數(shù)B對象返回戈鲁。這樣我們把A對象作為參數(shù)傳遞給外層函數(shù)C仇参,將其調(diào)用,但可以接受到一個(gè)綁定了形參func(這里就是A)的新函數(shù)B對象婆殿。注意這里的函數(shù)對象B和單純定義的函數(shù)B是不一樣的诈乒,因?yàn)樗蛥?shù)func綁定在了一起,是一個(gè)新的函數(shù)對象鸣皂。所以上面b和b2是兩個(gè)各自獨(dú)立的函數(shù)對象抓谴。
裝飾器就是為了解決上面的問題而存在的暮蹂,我們使用@符號為函數(shù)加上裝飾器。當(dāng)然我們函數(shù)A本身也是可以有參數(shù)的癌压,那么在函數(shù)C中我們最后返回的函數(shù)B對象也應(yīng)該定義對應(yīng)的參數(shù)才行仰泻,那么為了為了通用性,我們可以使用可擴(kuò)展參數(shù)滩届,也就是在B函數(shù)中只定義 *args 和 kwargs集侯, args用來接收所有的位置參數(shù),kwargs用來節(jié)后所有的關(guān)鍵字參數(shù)帜消。在B的代碼中棠枉,我們再將其解包以args 和 **kwargs 的形式傳給函數(shù)func 的調(diào)用。
在函數(shù)C外面再嵌套一層函數(shù)的方法蝎困, 不僅是因?yàn)槲覀兩厦嬲f的將B需要的參數(shù)和func 變量的作用域隔離開來录语。這里,我們再分析一下裝飾器的語法禾乘,裝飾器是在需要改造的函數(shù)的上面加一個(gè)@符號后面跟一個(gè)我們定義的改造函數(shù)名钦无。這個(gè)@加函數(shù)名的語法其實(shí)跟小括號一樣,會直接調(diào)用這個(gè)函數(shù)盖袭,并把下面裝飾的函數(shù)作為參數(shù)傳入。那么我們要給裝飾器傳入?yún)?shù)時(shí)彼宠,需要使用小括號鳄虱,那么小括號也是執(zhí)行函數(shù)的表達(dá)式。所以這個(gè)裝飾器函數(shù)在匹配上小括號和@符號時(shí)要被執(zhí)行兩次凭峡。而且是小括號先執(zhí)行拙已。所以我們必須在裝飾器函數(shù)中進(jìn)行兩次嵌套。這樣小括號表達(dá)式執(zhí)行了函數(shù)后將要返回一個(gè)函數(shù)對象C摧冀,C函數(shù)對象和@符號匹配將下面裝飾的函數(shù)A作為參數(shù)傳入倍踪,再次執(zhí)行并返回一個(gè)新的函數(shù)對象B系宫,并賦值給下面函數(shù)同名的變量名A。
注意@符號和() 都是函數(shù)調(diào)用語法建车。給一個(gè)函數(shù)加上裝飾器扩借,Python解釋器會直接執(zhí)行裝飾器函數(shù)。最后生成一個(gè)新的函數(shù)對象缤至,也就是上面例子中的A潮罪。
《Python基礎(chǔ)手冊》系列:
Python基礎(chǔ)手冊 1 —— Python語言介紹
Python基礎(chǔ)手冊 2 —— Python 環(huán)境搭建(Linux)
Python基礎(chǔ)手冊 3 —— Python解釋器
Python基礎(chǔ)手冊 4 —— 文本結(jié)構(gòu)
Python基礎(chǔ)手冊 5 —— 標(biāo)識符和關(guān)鍵字
Python基礎(chǔ)手冊 6 —— 操作符
Python基礎(chǔ)手冊 7 —— 內(nèi)建函數(shù)
Python基礎(chǔ)手冊 8 —— Python對象
Python基礎(chǔ)手冊 9 —— 數(shù)字類型
Python基礎(chǔ)手冊10 —— 序列(字符串)
Python基礎(chǔ)手冊11 —— 序列(元組&列表)
Python基礎(chǔ)手冊12 —— 序列(類型操作)
Python基礎(chǔ)手冊13 —— 映射(字典)
Python基礎(chǔ)手冊14 —— 集合
Python基礎(chǔ)手冊15 —— 解析
Python基礎(chǔ)手冊16 —— 文件
Python基礎(chǔ)手冊17 —— 簡單語句
Python基礎(chǔ)手冊18 —— 復(fù)合語句(流程控制語句)
Python基礎(chǔ)手冊19 —— 迭代器
Python基礎(chǔ)手冊20 —— 生成器
Python基礎(chǔ)手冊21 —— 函數(shù)的定義
Python基礎(chǔ)手冊22 —— 函數(shù)的參數(shù)
Python基礎(chǔ)手冊23 —— 函數(shù)的調(diào)用
Python基礎(chǔ)手冊24 —— 函數(shù)中變量的作用域
Python基礎(chǔ)手冊25 —— 裝飾器
Python基礎(chǔ)手冊26 —— 錯誤 & 異常
Python基礎(chǔ)手冊27 —— 模塊
Python基礎(chǔ)手冊28 —— 模塊的高級概念
Python基礎(chǔ)手冊29 —— 包