在介紹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
,此時就是普通裝飾器的流程了。
類裝飾器
我們經常會創建很多具有可讀寫屬性的類,這些類一般具有大量重複的getter
和setter
方法。比如,我們需要創建一個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
類型,然後內部設置了getter
和setter
方法,在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
屬性的值就保存在名爲__title
的attribute
中)。屬性的setter
函數還會調用原來Ensure
實例的驗證器。在第二部分裏,我們遍歷類中的每一個attribute
,並用新的屬性來替換原先的Ensure
實例。第三部分會把修改後的類返回。
執行完裝飾器後,受裝飾的類裏原有的Ensure
型attribute
都會被同名且帶有驗證機制的屬性所取代。
用類裝飾器實現繼承
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編程實戰》有所出入,但是一個現象各抒己見,學到東西纔是真的)。