一、裝飾器
裝飾器背后的主要動機源自 python 面向對象編程。裝飾器是在函數(shù)調用之上的修飾。這些修飾僅是當聲明一個函數(shù)或者方法的時候,才會應用的額外調用。
裝飾器的語法以 @
開頭,接著是裝飾器函數(shù)的名字和可選的參數(shù)。緊跟著裝飾器聲明的是被修飾的函數(shù),和裝飾函數(shù)的可選參數(shù)。
有參數(shù)和無參數(shù)的裝飾器
沒有參數(shù)的裝飾器:
帶參數(shù)的裝飾器 :
現(xiàn)在我們知道裝飾器實際就是函數(shù)。我們也知道他們接受函數(shù)對象。一般說來,當你包裝一個函數(shù)的時候,你可以在包裝的環(huán)境下在合適的時機調用這個函數(shù)。我們在執(zhí)行函數(shù)之前,可以運行些預備代碼,如 post-morrem 分析,也可以在執(zhí)行代碼之后做些清理工作。
你可以考慮在裝飾器中置入通用功能的代碼來降低程序復雜度。例如,可以用裝飾器來:
- 引入日志
- 增加計時邏輯來檢測性能
- 給函數(shù)加入事務的能力
對于用 python 創(chuàng)建企業(yè)級應用,支持裝飾器的特性是非常重要的。
二、裝飾器的原理探析
我們可以跟著下面的例子更深層次的理解裝飾器的原理。
當你要在一個函數(shù)A之前或者之后增加一些操作的話,我們可以定義一個新的函數(shù)B,在函數(shù)B中定義這些操作,并在指定的位置調用函數(shù)A。這樣我們就等到了一個新的函數(shù)對象,他封裝的對函數(shù)A執(zhí)行的額外的操作。并且可以把B函數(shù)名賦值給其他變量。那怎樣將func和B綁定到一個作用域呢?讓他們綁定到一起成為一個新的函數(shù)對象返回。要知道上面的例子中b和b2都是指向了統(tǒng)一個對象B,所以他們的調用結果才都是一樣的。
這時我們可以利用嵌套函數(shù)的一個特點,就是 在嵌套函數(shù)中,將內層函數(shù)對象作為返回值返回的話,內層函數(shù)對象可以保留其所在的作用域(外層函數(shù)的作用域),也就是外層函數(shù)中定義的一切變量都可以跟隨內層函數(shù)得到保留。 利用這個特性,因為形參也處于函數(shù)作用域中,所以我們讓外層函數(shù)來接受參數(shù),當外層函數(shù)調用結束后,返回的內層函數(shù)還處于一個封閉的作用域中,并可以使用外層函數(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,將其調用,但可以接受到一個綁定了形參func(這里就是A)的新函數(shù)B對象。注意這里的函數(shù)對象B和單純定義的函數(shù)B是不一樣的,因為他和參數(shù)func綁定在了一起,是一個新的函數(shù)對象。所以上面b和b2是兩個各自獨立的函數(shù)對象。
裝飾器就是為了解決上面的問題而存在的,我們使用@符號為函數(shù)加上裝飾器。當然我們函數(shù)A本身也是可以有參數(shù)的,那么在函數(shù)C中我們最后返回的函數(shù)B對象也應該定義對應的參數(shù)才行,那么為了為了通用性,我們可以使用可擴展參數(shù),也就是在B函數(shù)中只定義 *args 和 kwargs, args用來接收所有的位置參數(shù),kwargs用來節(jié)后所有的關鍵字參數(shù)。在B的代碼中,我們再將其解包以args 和 **kwargs 的形式傳給函數(shù)func 的調用。
在函數(shù)C外面再嵌套一層函數(shù)的方法, 不僅是因為我們上面說的將B需要的參數(shù)和func 變量的作用域隔離開來。這里,我們再分析一下裝飾器的語法,裝飾器是在需要改造的函數(shù)的上面加一個@符號后面跟一個我們定義的改造函數(shù)名。這個@加函數(shù)名的語法其實跟小括號一樣,會直接調用這個函數(shù),并把下面裝飾的函數(shù)作為參數(shù)傳入。那么我們要給裝飾器傳入?yún)?shù)時,需要使用小括號,那么小括號也是執(zhí)行函數(shù)的表達式。所以這個裝飾器函數(shù)在匹配上小括號和@符號時要被執(zhí)行兩次。而且是小括號先執(zhí)行。所以我們必須在裝飾器函數(shù)中進行兩次嵌套。這樣小括號表達式執(zhí)行了函數(shù)后將要返回一個函數(shù)對象C,C函數(shù)對象和@符號匹配將下面裝飾的函數(shù)A作為參數(shù)傳入,再次執(zhí)行并返回一個新的函數(shù)對象B,并賦值給下面函數(shù)同名的變量名A。
注意@符號和() 都是函數(shù)調用語法。給一個函數(shù)加上裝飾器,Python解釋器會直接執(zhí)行裝飾器函數(shù)。最后生成一個新的函數(shù)對象,也就是上面例子中的A。
《Python基礎手冊》系列:
Python基礎手冊 1 —— Python語言介紹
Python基礎手冊 2 —— Python 環(huán)境搭建(Linux)
Python基礎手冊 3 —— Python解釋器
Python基礎手冊 4 —— 文本結構
Python基礎手冊 5 —— 標識符和關鍵字
Python基礎手冊 6 —— 操作符
Python基礎手冊 7 —— 內建函數(shù)
Python基礎手冊 8 —— Python對象
Python基礎手冊 9 —— 數(shù)字類型
Python基礎手冊10 —— 序列(字符串)
Python基礎手冊11 —— 序列(元組&列表)
Python基礎手冊12 —— 序列(類型操作)
Python基礎手冊13 —— 映射(字典)
Python基礎手冊14 —— 集合
Python基礎手冊15 —— 解析
Python基礎手冊16 —— 文件
Python基礎手冊17 —— 簡單語句
Python基礎手冊18 —— 復合語句(流程控制語句)
Python基礎手冊19 —— 迭代器
Python基礎手冊20 —— 生成器
Python基礎手冊21 —— 函數(shù)的定義
Python基礎手冊22 —— 函數(shù)的參數(shù)
Python基礎手冊23 —— 函數(shù)的調用
Python基礎手冊24 —— 函數(shù)中變量的作用域
Python基礎手冊25 —— 裝飾器
Python基礎手冊26 —— 錯誤 & 異常
Python基礎手冊27 —— 模塊
Python基礎手冊28 —— 模塊的高級概念
Python基礎手冊29 —— 包