之前你有沒有這樣一段經歷:好久以前你寫過一個函數,如今你忽然有了個想法就是你想看看,之前那個函數在你數據集上的運行時間是多少,這時候你能夠修改以前代碼爲它加上計時的功能,可是這樣的話是否是還要大致讀讀你以前的這個的代碼,稍微搞清楚一點它的邏輯,纔敢給它添加新的東西。這樣是否是很繁瑣,要是你以前寫的代碼足夠亂足夠長,再去讀它是否是很抓狂...。實際工做中,咱們經常會遇到這樣的場景,可能你的需求還不僅是這麼簡單。那麼有沒有一種能夠不對源碼作任何修改,而且能夠很好的實現你全部需求的手段呢?答案固然是有,這就是今天咱們要介紹的python裝飾器。有了裝飾器,你除了不用擔憂前面提到的問題,而且還能夠很好的處理接下來要作的事:那就是如今你又有了一個新的需求,好比爲另外一個函數添加計時功能,這時就很是簡單了,把要裝飾的函數丟給裝飾器就行了,它會自動給你添加完功能並返回給你。是否是很神奇?下面咱們將一層層剝開它的神祕面紗。html
在看裝飾器以前,咱們先來搞清楚什麼是閉包函數。python是一種面向對象的編程語言,在python中一切皆對象,這樣就使得變量所擁有的屬性,函數也一樣擁有。這樣咱們就能夠理解在函數內建立一個函數的行爲是徹底合法的。這種函數被叫作內嵌函數,這種函數只能夠在外部函數的做用域內被正常調用,在外部函數的做用域以外調用會報錯,例如:python
def outFunction():
print('out side the function')
def inFunction():
print('inside the function')
inFunction()
outFunction()
inFunction()
而若是內部函數裏引用了外部函數裏定義的對象(甚至是外層以外,但不是全局變量),那麼此時內部函數就被稱爲閉包函數。閉包函數所引用的外部定義的變量被叫作自由變量。閉包從語法上看很是簡單,可是卻有強大的做用。閉包能夠將其本身的代碼和做用域以及外部函數的做用結合在一塊兒。下面給出一個簡單的閉包的例子:編程
def count():
a = 1
b = 1
def sum():
c = 1
return a + c # a - 自由變量
return sum # 注意返回的是一個sum()函數對象
result = count()
result
result()
總結:什麼函數能夠被稱爲閉包函數呢?主要是知足兩點:函數內部定義的函數;引用了外部變量但非全局變量。閉包
有了閉包函數的概念,咱們再去理解裝飾器會相對容易一些。python裝飾器本質上就是一個函數,它可讓其餘函數在不須要作任何代碼變更的前提下增長額外的功能,裝飾器的返回值也是一個函數對象(函數的指針)。裝飾器函數的外部函數傳入我要裝飾的函數名字,返回通過修飾後函數的名字;內層函數(閉包)負責修飾被修飾函數。從上面這段描述中咱們須要記住裝飾器的幾點屬性,以便後面能更好的理解:app
實質: 是一個函數編程語言
參數:是你要裝飾的函數名(並不是函數調用)ide
返回:是裝飾完的函數名(也非函數調用)函數
做用:爲已經存在的對象添加額外的功能性能
特色:不須要對對象作任何的代碼上的變更測試
python裝飾器有不少經典的應用場景,好比:插入日誌、性能測試、事務處理、權限校驗等。裝飾器是解決這類問題的絕佳設計。而且從引入中的列子中咱們也能夠概括出:裝飾器最大的做用就是對於咱們已經寫好的程序,咱們能夠抽離出一些雷同的代碼組建多個特定功能的裝飾器,這樣咱們就能夠針對不一樣的需求去使用特定的裝飾器,這時由於源碼去除了大量泛化的內容而使得源碼具備更加清晰的邏輯。
函數的函數裝飾器 咱們仍是覺得函數添加計時功能爲例,講述函數裝飾器。
import time
def decorator(func):
def wrapper(*args, **kwargs): # 這個wrapper函數要替換掉原來的func函數,func函數可能有形參,也可能沒有形參,也可能有多個形參
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表示的參數以字典的形式傳入:
def test(*args, **kwargs):
print('args:', end=' ')
print(args)
print('kwargs', end=' ')
print(kwargs)
test(["world"], 'world', 1, a=1, b=2)
從圖中咱們能夠看到:凡是以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,其餘的用法都和函數裝飾器相同。
class Decorator(object):
def __init__(self, f):
self.f = f
def __call__(self): # 這裏有注意的是:__call__()是一個特殊方法,它可將一個類實例變成一個可調用對象:
print("decorator start")
self.f()
print("decorator end")
@Decorator
def func():
print("func")
func()
p = Decorator(func) # p是類Decorator的一個實例
p() # 實現了__call__()方法後,p能夠被調用
def makebold(f):
return lambda: '<b>' +f() + '</b>'
def makeitalic(f):
return lambda: '<i>' +f() + '</i>'
@makebold
@makeitalic
def say():
return "hello"
say()
可見,多個裝飾器的執行順序:是從近到遠依次執行。
def decorator(func):
def inner_function():
pass
return inner_function
@decorator
def func():
pass
func.__name__
述代碼最後執行的結果不是 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
func.__name__