十、PYTHON 學習之裝飾器加深理解

在第六章已經有所介紹,這裏看到一篇比較好的文章轉過來。

基本概念

裝飾器是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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章