Python裝飾器在開發過程當中,有着較爲重要的地位,可是對於初學者來講,並不便於理解,本文將帶着你們分析python裝飾器的使用。javascript
裝飾器本質上就是一個函數,這個函數接受其餘函數做爲參數,並將其以一個新的修改後的函數做爲替換。
概念較爲抽象,咱們來考慮以下一個場景,如今咱們須要對用戶年齡進行認證,若是年齡小於18,則給出提示,年齡不符合要求(嘿嘿嘿,你們都懂)。代碼以下:java
class Movie(object):
def get_movie(self,age):
if age<18:
raise Exception('用戶年齡不符合要求')
return self.movie
def set_movie(self,age,movie):
if age <18:
raise Exception('用戶年齡不符合要求')
self.movie = movie複製代碼
考慮到複用性的問題,咱們對其修改:python
def check_age(age):
if age < 18:
raise Exception('用戶年齡不符合要求')
class User(object):
def get_movie(self, age):
check_age(age)
return self.movie
def set_movie(self, age, movie):
check_age(age)
self.movie = movie複製代碼
如今,代碼看起來整潔了一點,可是用裝飾器的話能夠作的更好:app
def check_age(f):
def wrapper(*args,**kwargs):
if args[1]<18:
raise Exception('用戶年齡不符合要求')
return f(*args,**kwargs)
return wrapper
class User(object):
@check_age
def get_movie(self, age):
return self.movie
@check_age
def set_movie(self, age, movie):
self.movie = movie複製代碼
上面這段代碼就是使用裝飾的一個典型例子,函數check_age中定義了另外一個函數wrapper,並將wrapper作爲返回值。這個例子很好的展現了裝飾器的語法。函數
上面說到裝飾器的本質就是一個函數,這個函數接受另外一個函數做爲參數,並將其其以一個新的修改後的函數進行替換。再來看下面一個例子,能夠幫咱們更好的理解:spa
def bread(func):
def wrapper():
print ("</''''''\>")
func()
print ("</______\>")
return wrapper
def sandwich():
print('- sandwich -')
sandwich_copy = bread(sandwich)
sandwich_copy()複製代碼
輸出結果以下:設計
</''''''\>
- sandwich -
</______\>複製代碼
bread是一個函數,它接受一個函數做爲參數,而後返回一個新的函數,新的函數對原來的函數進行了一些修改和擴展(打印一些東西),且這個新函數能夠當作普通函數進行調用。
使用python提供的裝飾器語法,簡化上面的代碼:code
def bread(func):
def wrapper():
print ("</''''''\>")
func()
print ("</______\>")
return wrapper
@bread
def sandwich():
print('- sandwich -')
sandwich = sandwich()複製代碼
到這裏,咱們應該理解了裝飾器的用法和做用了,再次強調一遍,裝飾器本質上就是一個函數,這個函數接受其餘的函數做爲參數,並將其以一個新的修改後的函數進行替換ip
前面咱們介紹了裝飾器的用法,能夠看出裝飾器其實很好理解,也很是簡單。可是裝飾器還有一些須要咱們注意的地方開發
裝飾器動態替換的新函數替換了原來的函數,可是,新函數缺乏不少原函數的屬性,如docstring和函數名。
def bread(func):
def wrapper():
print ("</''''''\>")
func()
print ("</______\>")
return wrapper
@bread
def sandwich():
'''there are something'''
print('- sandwich -')
def hamberger():
'''there are something'''
print('- hamberger -')
def main():
print(sandwich.__doc__)
print(sandwich.__name__)
print(hamberger.__doc__)
print(hamberger.__name__)
main()複製代碼
執行上面的程序,獲得以下結果:
None
wrapper
there are something
hamberger複製代碼
在上述代碼中,定義了兩個函數sandwich和hanberger,其中sandwich使用裝飾器@bread進行了封裝,咱們獲取sandwich和hanberger的docstring和函數名字,能夠看到,使用了裝飾器的函數,沒法正確獲取函數原有的docstring和名字,爲了解決這個問題,可使用python內置的functools模塊。
def bread(func):
@functools.wrap(func)
def wrapper():
print ("</''''''\>")
func()
print ("</______\>")
return wrapper複製代碼
咱們只須要增長一行代碼,就能正確的獲取函數的屬性。
此外,也能夠像下面這樣:
import functools
def bread(func):
def wrapper():
print ("</''''''\>")
func()
print ("</______\>")
return functools.wraps(func)(wrapper)複製代碼
不過,仍是第一種方法的可讀性要更強一點。
咱們再來看以下一段代碼:
def check_age(f):
@functools.wraps(f)
def wrapper(*args,**kwargs):
if kwargs.get('age')<18:
raise Exception('用戶年齡不符合要求')
return f(*args,**kwargs)
return wrapper
class User(object):
@check_age
def get_movie(self, age):
return self.movie
@check_age
def set_movie(self, age, movie):
self.movie = movie
user = User()
user.set_movie(19,'Avatar')複製代碼
這段代碼運行後會直接拋出,由於咱們傳入的'age'是一個位置參數,而咱們卻用關鍵字參數(kwargs)獲取用戶名,所以。‘kwargs.get('age')’返回None,None和int類型是沒法比較的,因此會拋出異常。
爲了設計一個更加智能的裝飾器,咱們須要使用python的inspect模塊。以下所示:
def check_age(f):
@functools.wraps(f)
def wrapper(*args,**kwargs):
getcallargs = inspect.getcallargs(f, *args, **kwargs)
print(getcallargs)
if getcallargs.get('age')<18:
raise Exception('用戶年齡不符合要求')
return f(*args,**kwargs)
return wrapper複製代碼
經過inspect.getcallargs,返回一個將參數名和值做爲鍵值對的字典,在上述代碼中,返回{'self': <__main__.user object="" at="" 0x10be19320="">, 'age': 19, 'movie': 'Avatar'},經過這種方式,咱們的裝飾器沒必要檢查參數username是基於位置參數仍是基於關鍵字參數,而只需在字典中查找便可。
在開發中,會出現對於一個函數使用兩個裝飾器進行包裝的狀況,代碼以下:
def bold(f):
def wrapper():
return "<b>"+f()+"</b>"
return wrapper
def italic(f):
def wrapper():
return "<i>"+f()+"</i>"
return wrapper
@bold
@italic
def hello():
return "hello world"
print(hello()) # <b><i>hello world</i></b>複製代碼
分析
在前面咱們提到,裝飾器就是在外層進行了封裝:
@italic
hello()
hello = italic(hello)複製代碼
對於兩層封裝即是:
@bold
@italic
hello()
hello = bold(italic(hello))複製代碼
這樣理解多個裝飾器的調用順序,以後就不會再有疑問了
如今,咱們的需求修改了,並非限定爲18歲了,對於不一樣的地區多是20歲,也多是16歲。那麼咱們如何設計一個通用的裝飾器呢?
def check_age(age='18'):
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
getcallargs = inspect.getcallargs(f, *args, **kwargs)
if getcallargs.get('age') < age:
raise Exception('用戶年齡不符合要求')
return f(*args, **kwargs)
return wrapper
return decorator
class User(object):
@check_age(18)
def get_movie(self, age):
return self.movie
@check_age(18)
def set_movie(self, age, movie):
check_age(age)
self.movie = movie
user = User()
user.set_movie(16,'Avatar')複製代碼
經過上述方式,咱們能夠在使用裝飾器時設置age的值,而不須要修改裝飾器內的代碼,使程序的健壯性更強,符合開閉原則。
到這裏,關於裝飾器的理解,咱們就介紹完了,配合在實際開發中的使用,你很快就能掌握它。