在第六章已經有所介紹,這裏看到一篇比較好的文章轉過來。
基本概念
裝飾器是23z種設計模式之一,經常被用於有切面需求的場景,較爲經典的有插入日誌、性能測試、事務處理, Web權限校驗, Cache等。
很有名的例子來理解,咖啡,加糖的咖啡,加牛奶的咖啡。 本質上,還是咖啡,只是在原有的東西上,做了“裝飾”,使之附加一些功能或特性。
例如記錄日誌,需要對某些函數進行記錄
笨的辦法,每個函數加入代碼,如果代碼變了,就悲催了
裝飾器的辦法,定義一個專門日誌記錄的裝飾器,對需要的函數進行裝飾。
優點
抽離出大量函數中與函數功能本身無關的雷同代碼並繼續重用
即,可以將函數“修飾”爲完全不同的行爲,可以有效的將業務邏輯正交分解,如用於將權限和身份驗證從業務中獨立出來
概括的講,裝飾器的作用就是爲已經存在的對象添加額外的功能
Python中的裝飾器
在Python中,裝飾器實現是十分方便的
原因是:函數可以被扔來扔去。
函數作爲一個對象:
A.可以被賦值給其他變量,可以作爲返回值 B.可以被定義在另外一個函數內
def:
裝飾器是一個函數,一個用來包裝函數的函數,裝飾器在函數申明完成的時候被調用,調用之後返回一個修改之後的函數對象,將其重新賦值原來的標識符,並永久喪失對原始函數對象的訪問(申明的函數被換成一個被裝飾器裝飾過後的函數)
當我們對某個方法應用了裝飾方法後, 其實就改變了被裝飾函數名稱所引用的函數代碼塊入口點,使其重新指向了由裝飾方法所返回的函數入口點。
由此我們可以用decorator改變某個原有函數的功能,添加各種操作,或者完全改變原有實現
分類:
裝飾器分爲無參數decorator,有參數decorator
* 無參數decorator 生成一個新的裝飾器函數 * 有參decorator 有參裝飾,裝飾函數先處理參數,再生成一個新的裝飾器函數,然後對函數進行裝飾
裝飾器有參/無參,函數有參/無參,組合共4種
具體定義:
decorator方法
A.把要裝飾的方法作爲輸入參數,
B.在函數體內可以進行任意的操作(可以想象其中蘊含的威力強大,會有很多應用場景),
C.只要確保最後返回一個可執行的函數即可(可以是原來的輸入參數函數, 或者是一個新函數)
無參數裝飾器 – 包裝無參數函數
不需要針對參數進行處理和優化
def decorator(func): print "hello" return func @decorator def foo(): pass foo()
foo() 等價於:
foo = decorator(foo) foo()
無參數裝飾器 – 包裝帶參數函數
def decorator_func_args(func): def handle_args(*args, **kwargs): #處理傳入函數的參數 print "begin" func(*args, **kwargs) #函數調用 print "end" return handle_args @decorator_func_args def foo2(a, b=2): print a, b foo2(1)
foo2(1) 等價於
foo2 = decorator_func_args(foo2) foo2(1)
帶參數裝飾器 – 包裝無參數函數
def decorator_with_params(arg_of_decorator):#這裏是裝飾器的參數 print arg_of_decorator #最終被返回的函數 def newDecorator(func): print func return func return newDecorator @decorator_with_params("deco_args") def foo3(): pass foo3()
與前面的不同在於:比上一層多了一層封裝,先傳遞參數,再傳遞函數名
第一個函數decomaker是裝飾函數,它的參數是用來加強“加強裝飾”的。由於此函數並非被裝飾的函數對象,所以在內部必須至少創建一個接受被裝飾函數的函數,然後返回這個對象(實際上此時foo3= decorator_with_params(arg_of_decorator)(foo3))
帶參數裝飾器– 包裝帶參數函數
def decorator_whith_params_and_func_args(arg_of_decorator): def handle_func(func): def handle_args(*args, **kwargs): print "begin" func(*args, **kwargs) print "end" print arg_of_decorator, func, args,kwargs return handle_args return handle_func @decorator_whith_params_and_func_args("123") def foo4(a, b=2): print "Content" foo4(1, b=3)
內置裝飾器
內置的裝飾器有三個:staticmethod,classmethod, property
class A(): @staticmethod def test_static(): print "static" def test_normal(self): print "normal" @classmethod def test_class(cls): print "class", cls a = A() A.test_static() a.test_static() a.test_normal() a.test_class()
結果:
static static normal class __main__.A
A.test_static
staticmethod 類中定義的實例方法變成靜態方法
基本上和一個全局函數差不多(不需要傳入self,只有一般的參數),只不過可以通過類或類的實例對象來調用,不會隱式地傳入任何參數。
類似於靜態語言中的靜態方法
B.test_normal
普通對象方法: 普通對象方法至少需要一個self參數,代表類對象實例
C.test_class
類中定義的實例方法變成類方法
classmethod需要傳入類對象,可以通過實例和類對象進行調用。
是和一個class相關的方法,可以通過類或類實例調用,並將該class對象(不是class的實例對象)隱式地當作第一個參數傳入。
就這種方法可能會 比較奇怪一點,不過只要你搞清楚了python裏class也是個真實地存在於內存中的對象,而不是靜態語言中只存在於編譯期間的類型,就好辦了。正常的方法就是和一個類的實例對象相關的方法,通過類實例對象進行調用,並將該實例對象隱式地作爲第一個參數傳入,這個也和其它語言比較像。
D.區別
staticmethod,classmethod相當於全局方法,一般用在抽象類或父類中。一般與具體的類無關。
類方法需要額外的類變量cls,當有子類繼承時,調用類方法傳入的類變量cls是子類,而不是父類。
類方法和靜態方法都可以通過類對象和類的實例對象訪問
定義方式,傳入的參數,調用方式都不相同。
E.property
對類屬性的操作,類似於java中定義getter/setter
class B(): def __init__(self): self.__prop = 1 @property def prop(self): print "call get" return self.__prop @prop.setter def prop(self, value): print "call set" self.__prop = value @prop.deleter def prop(self): print "call del" del self.__prop
其他
A.裝飾器的順序很重要,需要注意
@A @B @C def f ():
等價於
f = A(B(C(f)))
B.decorator的作用對象可以是模塊級的方法或者類方法
C.functools模塊提供了兩個裝飾器。 這個模塊是Python 2.5後新增的。
functools.wraps(func) total_ordering(cls) 這個具體自己去看吧,後續用到了再補充
一個簡單例子
通過一個變量,控制調用函數時是否統計時間
#!/usr/bin/env python # -*- coding:utf-8 -*- #@author: [email protected] #@version: a test of decorator #@date: 20121027 #@desc: just a test import logging from time import time logger = logging.getLogger() logger.setLevel(logging.DEBUG) is_debug = True def count_time(is_debug): def handle_func(func): def handle_args(*args, **kwargs): if is_debug: begin = time() func(*args, **kwargs) logging.debug( "[" + func.__name__ + "] -> " + str(time() - begin) ) else: func(*args, **kwargs) return handle_args return handle_func def pr(): for i in range(1,1000000): i = i * 2 print "hello world" def test(): pr() @count_time(is_debug) def test2(): pr() @count_time(False) def test3(): pr() if __name__ == "__main__": test() test2() test3()
結果:
hello world hello world DEBUG:root:[test2] -> 0.0748538970947 hello world