以前你有沒有這樣一段經歷:很久之前你寫過一個函數,現在你突然有了個想法就是你想看看,以前那個函數在你數據集上的運行時間是多少,這時候你可以修改之前代碼為它加上計時的功能,但是這樣的話是不是還要大體讀讀你之前的這個的代碼,稍微搞清楚一點它的邏輯,才敢給它添加新的東西。這樣是不是很繁瑣,要是你之前寫的代碼足夠亂足夠長,再去讀它是不是很抓狂...。實際工作中,我們常常會遇到這樣的場景,可能你的需求還不只是這么簡單。那么有沒有一種可以不對源碼做任何修改,並且可以很好的實現你所有需求的手段呢?答案當然是有,這就是今天我們要介紹的python裝飾器。有了裝飾器,你除了不用擔心前面提到的問題,並且還可以很好的處理接下來要做的事:那就是現在你又有了一個新的需求,比如為另一個函數添加計時功能,這時就非常簡單了,把要裝飾的函數丟給裝飾器就好了,它會自動給你添加完功能並返回給你。是不是很神奇?下面我們將一層層剝開它的神秘面紗。
1. 閉包函數
在看裝飾器之前,我們先來搞清楚什么是閉包函數。python是一種面向對象的編程語言,在python中一切皆對象,這樣就使得變量所擁有的屬性,函數也同樣擁有。這樣我們就可以理解在函數內創建一個函數的行為是完全合法的。這種函數被叫做內嵌函數,這種函數只可以在外部函數的作用域內被正常調用,在外部函數的作用域之外調用會報錯,例如:
而如果內部函數里引用了外部函數里定義的對象(甚至是外層之外,但不是全局變量),那么此時內部函數就被稱為閉包函數。閉包函數所引用的外部定義的變量被叫做自由變量。閉包從語法上看非常簡單,但是卻有強大的作用。閉包可以將其自己的代碼和作用域以及外部函數的作用結合在一起。下面給出一個簡單的閉包的例子:
def count(): a = 1 b = 1 def sum(): c = 1 return a + c # a - 自由變量 return sum
總結:什么函數可以被稱為閉包函數呢?主要是滿足兩點:函數內部定義的函數;引用了外部變量但非全局變量。
2. python裝飾器
有了閉包函數的概念,我們再去理解裝飾器會相對容易一些。python裝飾器本質上就是一個函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外的功能,裝飾器的返回值也是一個函數對象(函數的指針)。裝飾器函數的外部函數傳入我要裝飾的函數名字,返回經過修飾后函數的名字;內層函數(閉包)負責修飾被修飾函數。從上面這段描述中我們需要記住裝飾器的幾點屬性,以便后面能更好的理解:
實質: 是一個函數
參數:是你要裝飾的函數名(並非函數調用)
返回:是裝飾完的函數名(也非函數調用)
作用:為已經存在的對象添加額外的功能
特點:不需要對對象做任何的代碼上的變動
python裝飾器有很多經典的應用場景,比如:插入日志、性能測試、事務處理、權限校驗等。裝飾器是解決這類問題的絕佳設計。並且從引入中的列子中我們也可以歸納出:裝飾器最大的作用就是對於我們已經寫好的程序,我們可以抽離出一些雷同的代碼組建多個特定功能的裝飾器,這樣我們就可以針對不同的需求去使用特定的裝飾器,這時因為源碼去除了大量泛化的內容而使得源碼具有更加清晰的邏輯。
2.1 函數裝飾器
函數的函數裝飾器
我們還是以為函數添加計時功能為例,講述函數裝飾器。
import time def decorator(func): def wrapper(*args, **kwargs): start_time = time.time() func() end_time = time.time() print(end_time - start_time) return wrapper @decorator def func(): time.sleep(0.8) func() # 函數調用
# 輸出:0.800644397735595
在上面代碼中 func是我要裝飾器的函數,我想用裝飾器顯示func函數運行的時間。@decorator這個語法相當於 執行 func = decorator(func),為func函數裝飾並返回。在來看一下我們的裝飾器函數 - decorator,該函數的傳入參數是func (被裝飾函數),返回參數是內層函數。這里的內層函數-wrapper,其實就相當於閉包函數,它起到裝飾給定函數的作用,wrapper參數為*args, **kwargs。*args表示的參數以列表的形式傳入;**kwargs表示的參數以字典的形式傳入:
從圖中我們可以看到:凡是以key=value形式的參數均存在kwargs中,剩下的所有參數都以列表的形式存於args中。這里要注意的是:為了不破壞原函數的邏輯,我們要保證內層函數wrapper和被裝飾函數func的傳入參數和返回值類型必須保持一致。
類方法的函數裝飾器
類方法的函數裝飾器和函數的函數裝飾器類似。
import time def decorator(func): def wrapper(me_instance): start_time = time.time() func(me_instance) end_time = time.time() print(end_time - start_time) return wrapper class Method(object): @decorator def func(self): time.sleep(0.8) p1 = Method()
p1.func() # 函數調用
對於類方法來說,都會有一個默認的參數self,它實際表示的是類的一個實例,所以在裝飾器的內部函數wrapper也要傳入一個參數 - me_instance就表示將類的實例p1傳給wrapper,其他的用法都和函數裝飾器相同。
2.2 類裝飾器
前面我們提到的都是讓 函數作為裝飾器去裝飾其他的函數或者方法,那么可不可以讓 一個類發揮裝飾器的作用呢?答案肯定是可以的,一切皆對象嚒,函數和類本質沒有什么不一樣。類的裝飾器是什么樣子的呢?
class Decorator(object): def __init__(self, f): self.f = f def __call__(self): print("decorator start") self.f() print("decorator end") @Decorator def func(): print("func") func()
這里有注意的是:__call__()是一個特殊方法,它可將一個類實例變成一個可調用對象:
p = Decorator(func) # p是類Decorator的一個實例 p() # 實現了__call__()方法后,p可以被調用
要使用類裝飾器必須實現類中的__call__()方法,就相當於將實例變成了一個方法。
2.3 裝飾器鏈
一個python函數也可以被多個裝飾器修飾,要是有多個裝飾器時,這些裝飾器的執行順序是怎么樣的呢?
可見,多個裝飾器的執行順序:是從近到遠依次執行。
2.4 python裝飾器庫 - functools
def decorator(func): def inner_function(): pass return inner_function @decorator def func(): pass print(func.__name__) # 輸出: inner_function
上述代碼最后執行的結果不是 func,而是 inner_function!這表示被裝飾函數自身的信息丟失了!怎么才能避免這種問題的發生呢?
可以借助functools.wraps()函數:
from functools import wraps def decorator(func): @wraps(func) def inner_function(): pass return inner_function @decorator def func(): pass print(func.__name__) #輸出: func