Python裝飾器

在介紹Python的裝飾器之前,先得介紹介紹裝飾模式(Decorator)。修飾模式主要是動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更爲靈活。

而正式因爲裝飾模式非常有用,所以Python提供了原生支持。在Python語言中,函數與方法都可以用裝飾器來修飾。此外,還有”類裝飾器”(class decorator),它也是個單參數的函數,其參數是類,由這種裝飾器所返回的新類的名稱與原類相同,但功能更多。有時可以通過類裝飾器來實現繼承。


函數裝飾器與方法裝飾器

創建一個裝飾器的流程:
1. 創建”包裝函數”(wrapper function, 我們多以wrapper()來命名)。
2. 在包裝函數裏調用原函數。調用前可以進行預處理,獲取結果之後,還可以執行後加工。包裝函數的返回值也很靈活:可以把原函數的調用結果直接返回,也可以先修改再返回,還可以返回其他值。
3. 最後,裝飾器把包裝函數作爲調用結果返回,返回後的函數會以原函數的名義將其取代。

裝飾器通常與@開頭,放於def/class上方,縮緊相同。

先來看看一個裝飾器裝飾的函數形式:

@float_arg_and_return
def mean(first, second, *rest):
    numbers = (first, second) + rest
    return sum(number)/len(number)

其實上面的代碼可以與下面的代碼等價:

def mean(first, second, *rest):
    numbers = (first, second) + rest
    return sum(number)/len(number)
mean = float_args_and_return(mean)

也就是說裝飾器就是一種語法糖(多吃糖長身體- -)。

接下來介紹float_args_and_return

def float_args_and_return(function):
    def wrapper(*args, **kwargs):
        args = [float(arg) for arg in args]
        return float(function(*args, **kwargs))
    return wrapper

可以看出function表示傳入裝飾器的函數,*args, **kwargs代表傳入函數的參數,這麼寫也是函數的所有參數的意思。

現在我們可以看出,mean這個函數接收兩個或多個任意類型的參數,並將這些參數均轉換爲float類型(並未處理異常),然後返回一個這些數的平均值。

但是,上面方法並沒有實現完美的包裝,因爲進過裝飾之後的函數的__name__屬性就會變爲包裝函數的__name__,而且即便原函數有docstring也將不存在了,在這裏,Python的標準庫爲我們提供了@functools.wraps裝飾器來解決它,我們可以在裝飾器裏用它來裝飾包裝函數以解決問題:

def float_args_and_return(function):
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        args = [float(arg) for arg in args]
        return float(function(*args, **kwargs))
    return wrapper

那現在如果你想在裝飾器中傳遞參數如何才能做到呢?定義一個能製作裝飾器的函數!


帶參數的裝飾器

比如我們希望製作一個裝飾器,函數運行時打印log,並打印該函數作者名字進入log,但是可能一個程序是多人合作的結果,此時我們可以將作者名字通過裝飾器參數傳入,這是我們最終呈現的形式:

@log('Bing')
def dosomething(*args):
    print '這只是個測試而已...'

我們先說明一下編寫裝飾器工廠(也就是帶參數的裝飾,等會就知道爲何叫做裝飾器工廠了)的一般流程:
1. 首先,創建裝飾器函數,在該函數內創建包裝函數,包裝函數的編寫方式如前所述。
2. 在包裝函數尾部,把調用原函數所得的返回值(也可以修改返回值,或用其他值來替換)返回給上一層。在裝飾器函數尾部返回包裝函數。
3. 最後,在裝飾器工廠函數尾部返回裝飾器

那log則如下編寫:

def log(author):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print 'run %s, the author is %s' %(func.__name__, author)
            return func(*args, **kwargs)
        return wrapper
    return decorator

創建好包裝函數之後,裝飾器會將其返回,而log()函數又會在其末尾將裝飾器返回。當Python執行到@log('Bing')的時候,它會先調用log()函數,這個函數會返回decorator()函數(這也是它被稱爲裝飾器工廠的原因),現在再看@,Python看到@之後會執行decorator函數,並把log後面的那個函數(do_somethind)傳給decorator,此時就是普通裝飾器的流程了。


類裝飾器

我們經常會創建很多具有可讀寫屬性的類,這些類一般具有大量重複的gettersetter方法。比如,我們需要創建一個Book類,此時需要四個屬性:title, ISBN, price, quantity,這時按照一般的方法我們可能需要四個@property修飾器,其代碼還都機會一直,此外,還需要四個setter方法,每個方法都要驗證用戶傳入的參數,而驗證價格與驗證數量都屬於驗證範圍,也是很相似,這樣勢必造成不必要的重複代碼。此時,我麼就需要使用到類裝飾器了,如:

@ensure("title", is_non_empty_str)
@ensure("isbn", is_valid_isbn)
@ensure("price", is_in_range(1, 10000))
@ensure("quantity", is_in_range(0, 1000000))
class Book:

    def __init__(self, title, isbn, price, quantity):
        self.title = title
        self.isbn = isbn
        self.price = price
        self.quantity = quantity


    @property
    def value(self):
        return self.price * self.quantity


    def __repr__(self):
        return ("Book({0.title!r}, {0.isbn!r}, {0.price!r}, "
                "{0.quantity!r})".format(self))

其中ensure作爲類裝飾器函數工廠,接收兩個參數,一個是屬性名,另一個是驗證器函數,然後返回一個類裝飾器,這個類裝飾器會運用在@ensure之後的類上面。

其實整個裝飾流程應該等同於:

ensure("title", is_non_empty_str)(
    ensure("isbn", is_valid_isbn)(
        ensure("price", is_in_range(1, 10000))(
            ensure("quantity", is_in_range(0, 1000000))(class Book:...))))

接下來看看ensure函數

def ensure(name, validate, doc=None):
    """This class decorator factory needs a property name and a validate
    function, and will accept a property docstring

    The validate function should return None on success and raise a
    ValueError on failure.
    """
    def decorator(Class):
        privateName = "__" + name
        def getter(self):
            return getattr(self, privateName)
        def setter(self, value):
            validate(name, value)
            setattr(self, privateName, value)
        setattr(Class, name, property(getter, setter, doc=doc))
        return Class
    return decorator

我麼可以看出它將給定的函數名全部設置爲"__"+name類型,然後內部設置了gettersetter方法,在setter方法中還調用了驗證函數。


用類裝飾器新增屬性

我們這裏準備將上述的多個裝飾器轉換爲只用一個類裝飾器

@do_ensure
class Book:

    title = Ensure(is_non_empty_str)
    isbn = Ensure(is_valid_isbn)
    price = Ensure(is_in_range(1, 10000))
    quantity = Ensure(is_in_range(0, 1000000))

    def __init__(self, title, isbn, price, quantity):
        self.title = title
        self.isbn = isbn
        self.price = price
        self.quantity = quantity

    @property
    def value(self):
        return self.price * self.quantity

上面是改寫後的Book類,我們使用@do_ensure類裝飾器與Ensure實例與前例相同的功能。每次構造Ensure對象時,都要傳入驗證函數,而@do_ensure類裝飾器會用帶有驗證機制的同名屬性來替換相應的Ensure實例。

class Ensure(object):

    def __init__(self, validate, doc=None):
        self.validate = validate
        self.doc = doc

def do_ensure(Class):
    def make_property(name, attribute):
        privateName = "__" + name
        def getter(self):
            return getattr(self, privateName)
        def setter(self, value):
            attribute.validate(name, value)
            setattr(self, privateName, value)
        return property(getter, setter, doc=attribute.doc)
    for name, attribute in Class.__dict__.items():
        if isinstance(attribute, Ensure):
            setattr(Class, name, make_property(name, attribute))
    return Class

上面這個類裝飾器可分爲三部分。在第一部分裏,我們定義了名爲make_property()的”嵌套函數”。該函數有兩個參數,一個是屬性名(比如”title”),另一個是Ensure類型的attribute,此函數將返回一個屬性,該屬性會把其值保存在私有的attribute中(比如title屬性的值就保存在名爲__titleattribute中)。屬性的setter函數還會調用原來Ensure實例的驗證器。在第二部分裏,我們遍歷類中的每一個attribute,並用新的屬性來替換原先的Ensure實例。第三部分會把修改後的類返回。

執行完裝飾器後,受裝飾的類裏原有的Ensureattribute都會被同名且帶有驗證機制的屬性所取代。


用類裝飾器實現繼承

def mediated(Class):
    setattr(Class, 'mediator', None)
    def on_change(self):
        if self.mediator is not None:
            self.mediator.on_change(self)
    setattr(Class, 'on_change', on_change)
    return Class

@mediated
class Button(object):
    def __init__(self, text=''):
        super().__init__()
        self.enabled = True
        self.text = text

    def click(self):
        if self.enabled:
            self.on_change()

其效果等同於:

class Mediated(object):

    def __init__(self):
        self.mediator = None

    def on_change(self):
        if self.mediator is not None:
            self.mediator.on_change(self)

class Button(Mediated):
    pass

最後仰慕般地推薦文章:
Python修飾器的函數式編程
大神講得詳細了很多(雖然文章之間有大神對裝飾器和裝飾器模式與《Python編程實戰》有所出入,但是一個現象各抒己見,學到東西纔是真的)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章