Python第二十六天 python裝飾器html
裝飾器
Python 2.4 開始提供了裝飾器( decorator ),裝飾器做爲修改函數的一種便捷方式,爲工程師編寫程序提供了便利性和靈活性
裝飾器本質上就是一個函數,這個函數接受其餘函數做爲參數,並將其以一個新的修改後的函數進行替換。python
裝飾器的做用
一、注入參數。爲函數提供默認參數,生成新的參數等
二、記錄函數的行爲。能夠統計函數的調用次數,緩存函數的結果,計算函數調用耗費的時間
三、預處理與後處理
四、修改調用時的上下文編程
函數能夠賦值給另一個變量名
函數能夠嵌套
函數對象能夠做爲另一個函數的參數
裝飾器僅僅是利用前面的這些Python 知識加上Python 語法實現的一種高級語法緩存
函數對象
在Python 語言中, def 語句定義了一個函數對象,並將其賦值給函數名。
也就是說,函數名其實只是一個變量,這個變量引用了這個函數對象。
所以,咱們能夠將函數賦值給另一個變量名,經過這個新的變量名調用函數。app
def say_hi(): print ("HI") hello = say_hi hello()
嵌套函數
在Python 語言中, def 語句是一個實時執行的語句,當它運行的時候會建立一個新的函數對象,並將其賦值給一個變量名
這裏所說的變量名就是函數的名稱。由於def 是一個語句,所以,函數定義能夠出如今其餘語句之中。編程語言
def outer(x , y) : def inner () : return x + y return inner f = outer(l , 2) print(f())
在這個例子中,咱們定義了一個名爲outer 的函數, 並在outer 函數內部定義了inner 函數
outer 函數以返回值的形式返回inner 函數,咱們將返回值保存在變量f 中,f 引用的是outer 函數內部的inner 函數
因此,當咱們調用函數f 時,實際是調用的inner 函數函數
回調函數
回調函數是指將函數做爲參數傳遞給另一個函數,並在另一個函數中進行調用。
回調函數並非Python 語言特有的,在各個編程語言中都存在。ui
def greeting(f): f() def say_hi(): print ("HI") def say_hello(): print("HELLO") greeting(say_hi) greeting(say_hello)
裝飾器
在Python 的裝飾器語法中,內層函數的參數是被裝飾的函數參數,外層函數的參數是被裝飾的函數。spa
def say_hi(): print("hi") def bread(f): def wrapper(*args, **kwargs ): print("begin call {0}".format(f.__name__)) f() print("finish call {0}".format(f.__name__)) return wrapper say_hi_copy = bread(say_hi) say_hi_copy()
上面這段代碼的執行結果以下:
begin call say_hi
hi
finish call say_hipwa
改造爲裝飾器
@bread def say_hi(username='someone'): #被裝飾函數,被裝飾函數的參數必定要用關鍵字參數,不能用位置參數,不然不能裝入字典!!!!!!! print("hi") print(username) def bread(f): #裝飾器函數 def wrapper(*args, **kwargs ): print("begin") f() print("finish") print(kwargs.get("username")) #讀取被裝飾函數的參數,被裝飾函數的參數必定要用關鍵字參數,不能用位置參數,不然不能裝入字典 return wrapper say_hi() 這段程序和前面的程序做用如出一轍,產生的結果也相同。區別在於,前面的程序顯示地調用了bread 函數來封裝say_hi 函數 這段程序經過Python 的語法糖來封裝say_hi函數。在Python 中, say_hi 函數定義語句前一行的"@bread"語句表示對該函數應用bread裝飾器 其中,"@"是裝飾器的語法,"bread"是裝飾器的名稱
獲取正確函數屬性
對於一個函數,咱們能夠經過name 屬性獲得函數的名字,經過doc 屬性獲得函數的幫助信息。
可是, 一個被裝飾器裝飾過的函數,默認狀況下,咱們經過doc 和name 獲取屬性時,獲得的倒是裝飾器中嵌套函數的信息
標準庫的functools 模塊中的wraps 裝飾器。wraps 裝飾器的做用是,複製函數屬性至被裝飾的函數
使用functools.wraps 裝飾器裝飾wrapper 函數。經過這樣簡單的修改就能夠獲取到add 函數的正確屬性
from __future__ import print_function import time import functools def benchmark(func): @functools.wraps(func) def wrapper(*args , **kwargs): t = time.time() res = func(*args , ** kwargs) print(func.__name__, time.time() - t) return res return wrapper @benchmark def add (a, b): '''Calculate the sum of two numbers''' return a + b print(add.__name__) print(add.__doc__)
inspect 模塊
inspect 模塊提供了許多有用的函數來獲取活躍對象的信息。其中, getcallargs 函數用來獲取函數的參數信息
getcallargs 會返回一個字典,該字典保存了函數的全部參數,包括關鍵字參數和位置參數。
也就是說, getcallargs 可以根據函數的定義和傳遞給函數的參數,推測出哪個值傳遞給函數的哪個參數。
getcallargs 推測出這些信息之後,以一個字典的形式返回給咱們全部的參數和取值。
import functools import inspect def check_is_admin(func): @functools.wraps(func) def wrapper(* args, * *kwargs): func_args = inspect.getcallargs(func , *args , ** kwargs) if func_args.get('username')! = 'admin' : raise Exception("This user is not allowed to put/get elem") return f( * args, * *kwargs ) return wrapper
給裝飾器傳遞參數
在Python 的裝飾器語法中,內層函數的參數是被裝飾的函數參數,外層函數的參數是被裝飾的函數。
那麼,若是裝飾器自己也有參數應該怎麼辦呢?在Python 的裝飾器語法中, 若是裝飾器自己也有參數,則須要再嵌套一層函數。
這也是爲何讀者看到的Python裝飾器有時候是兩層嵌套, 有時候是三層嵌套的緣由
下面是一個帶參數的裝飾器。在這個裝飾器的實現中,最外層的函數是裝飾器的名稱。
這個裝飾器的做用是將被裝飾的函數執行屢次。具體執行的次數由裝飾器的參數指定
from __future__ import print_function def times(length=1): def bread(func): def wrapper(* args ,* *kwargs): for i in range(length): func(*args, **kwargs) # func表明被裝飾函數sandwich(),因此被裝飾函數有什麼參數這裏就要寫什麼,例如Django裏面每一個view函數都有request參數,裝飾view函數就要寫成func(request, *args, **kwargs) return wrapper return bread @times(5) #5傳入到length,執行5次sandwich def sandwich(name): print (name) sandwich('hello')
裝飾器的前後順序
例子中,never_cache就要先於login_required被調用。
decorators = [never_cache, login_required] @method_decorator(decorators, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html'
或
@method_decorator(never_cache, name='dispatch') @method_decorator(login_required, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html'
使用method_decorator有時會致使TypeError異常,由於參數傳遞的緣由。固然,通常人碰不到的,碰獲得的人都不通常,都能本身查Django官方文檔掌握問題緣由。
參考
http://www.liujiangblog.com/blog/37/
def record_functime(): ''' 函數執行時間統計裝飾器 :return: ''' def stopwatch(callback): @functools.wraps(callback) def wrapper(*args, **kwargs): start = time.time() body = callback(*args, **kwargs) end = time.time() timelog = 'function name:'+callback.__name__ +'; execute time:' +str(start) +'; elapsed time:'+str(end - start) log_helper.info(timelog) return body return wrapper return stopwatch