日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
詳解Python的裝飾器

Python中的裝飾器是你進(jìn)入Python大門的一道坎,不管你跨不跨過去它都在那里。

為什么需要裝飾器

我們假設(shè)你的程序?qū)崿F(xiàn)了say_hello()和say_goodbye()兩個(gè)函數(shù)。

 
 
  1. def say_hello():
  2.     print "hello!"
  3.     
  4. def say_goodbye():
  5.     print "hello!"  # bug here
  6. if __name__ == '__main__':
  7.     say_hello()
  8.     say_goodbye() 

但是在實(shí)際調(diào)用中,我們發(fā)現(xiàn)程序出錯(cuò)了,上面的代碼打印了兩個(gè)hello。經(jīng)過調(diào)試你發(fā)現(xiàn)是say_goodbye()出錯(cuò)了。老板要求調(diào)用每個(gè)方法前都要記錄進(jìn)入函數(shù)的名稱,比如這樣:

 
 
  1. [DEBUG]: Enter say_hello()
  2. Hello!
  3. [DEBUG]: Enter say_goodbye()
  4. Goodbye! 

好,小A是個(gè)畢業(yè)生,他是這樣實(shí)現(xiàn)的。

 
 
  1. def say_hello():
  2.     print "[DEBUG]: enter say_hello()"
  3.     print "hello!"
  4. def say_goodbye():
  5.     print "[DEBUG]: enter say_goodbye()"
  6.     print "hello!"
  7. if __name__ == '__main__':
  8.     say_hello()
  9.     say_goodbye() 

很low吧? 嗯是的。小B工作有一段時(shí)間了,他告訴小A可以這樣寫。

 
 
  1. def debug():
  2.     import inspect
  3.     caller_name = inspect.stack()[1][3]
  4.     print "[DEBUG]: enter {}()".format(caller_name)   
  5. def say_hello():
  6.     debug()
  7.     print "hello!"
  8. def say_goodbye():
  9.     debug()
  10.     print "goodbye!"
  11. if __name__ == '__main__':
  12.     say_hello()
  13.     say_goodbye() 

是不是好一點(diǎn)?那當(dāng)然,但是每個(gè)業(yè)務(wù)函數(shù)里都要調(diào)用一下debug()函數(shù),是不是很難受?萬一老板說say相關(guān)的函數(shù)不用debug,do相關(guān)的才需要呢?

那么裝飾器這時(shí)候應(yīng)該登場了。

裝飾器本質(zhì)上是一個(gè)Python函數(shù),它可以讓其他函數(shù)在不需要做任何代碼變動(dòng)的前提下增加額外功能,裝飾器的返回值也是一個(gè)函數(shù)對象。它經(jīng)常用于有切面需求的場景,比如:插入日志、性能測試、事務(wù)處理、緩存、權(quán)限校驗(yàn)等場景。裝飾器是解決這類問題的***設(shè)計(jì),有了裝飾器,我們就可以抽離出大量與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。

概括的講,裝飾器的作用就是為已經(jīng)存在的函數(shù)或?qū)ο筇砑宇~外的功能。

怎么寫一個(gè)裝飾器

在早些時(shí)候 (Python Version < 2.4,2004年以前),為一個(gè)函數(shù)添加額外功能的寫法是這樣的。

 
 
  1. def debug(func):
  2.     def wrapper():
  3.         print "[DEBUG]: enter {}()".format(func.__name__)
  4.         return func()
  5.     return wrapper
  6. def say_hello():
  7.     print "hello!"
  8. say_hello = debug(say_hello)  # 添加功能并保持原函數(shù)名不變 

上面的debug函數(shù)其實(shí)已經(jīng)是一個(gè)裝飾器了,它對原函數(shù)做了包裝并返回了另外一個(gè)函數(shù),額外添加了一些功能。因?yàn)檫@樣寫實(shí)在不太優(yōu)雅,在后面版本的Python中支持了@語法糖,下面代碼等同于早期的寫法。

 
 
  1. def debug(func):
  2.     def wrapper():
  3.         print "[DEBUG]: enter {}()".format(func.__name__)
  4.         return func()
  5.     return wrapper
  6. @debug
  7. def say_hello():
  8.     print "hello!" 

這是最簡單的裝飾器,但是有一個(gè)問題,如果被裝飾的函數(shù)需要傳入?yún)?shù),那么這個(gè)裝飾器就壞了。因?yàn)榉祷氐暮瘮?shù)并不能接受參數(shù),你可以指定裝飾器函數(shù)wrapper接受和原函數(shù)一樣的參數(shù),比如:

 
 
  1. def debug(func):
  2.     def wrapper(something):  # 指定一毛一樣的參數(shù)
  3.         print "[DEBUG]: enter {}()".format(func.__name__)
  4.         return func(something)
  5.     return wrapper  # 返回包裝過函數(shù)
  6. @debug
  7. def say(something):
  8.     print "hello {}!".format(something) 

這樣你就解決了一個(gè)問題,但又多了N個(gè)問題。因?yàn)楹瘮?shù)有千千萬,你只管你自己的函數(shù),別人的函數(shù)參數(shù)是什么樣子,鬼知道?還好Python提供了可變參數(shù)*args和關(guān)鍵字參數(shù)**kwargs,有了這兩個(gè)參數(shù),裝飾器就可以用于任意目標(biāo)函數(shù)了。

 
 
  1. def debug(func):
  2.     def wrapper(*args, **kwargs):  # 指定宇宙無敵參數(shù)
  3.         print "[DEBUG]: enter {}()".format(func.__name__)
  4.         print 'Prepare and say...',
  5.         return func(*args, **kwargs)
  6.     return wrapper  # 返回
  7. @debug
  8. def say(something):
  9.     print "hello {}!".format(something) 

至此,你已完全掌握初級的裝飾器寫法。

高級一點(diǎn)的裝飾器

帶參數(shù)的裝飾器和類裝飾器屬于進(jìn)階的內(nèi)容。在理解這些裝飾器之前,***對函數(shù)的閉包和裝飾器的接口約定有一定了解。(參見http://betacat.online/posts/p...

帶參數(shù)的裝飾器

假設(shè)我們前文的裝飾器需要完成的功能不僅僅是能在進(jìn)入某個(gè)函數(shù)后打出log信息,而且還需指定log的級別,那么裝飾器就會(huì)是這樣的。

 
 
  1. def logging(level):
  2.     def wrapper(func):
  3.         def inner_wrapper(*args, **kwargs):
  4.             print "[{level}]: enter function {func}()".format(
  5.                 level=level,
  6.                 func=func.__name__)
  7.             return func(*args, **kwargs)
  8.         return inner_wrapper
  9.     return wrapper
  10. @logging(level='INFO')
  11. def say(something):
  12.     print "say {}!".format(something)
  13. # 如果沒有使用@語法,等同于
  14. # say = logging(level='INFO')(say)
  15. @logging(level='DEBUG')
  16. def do(something):
  17.     print "do {}...".format(something)
  18. if __name__ == '__main__':
  19.     say('hello')
  20.     do("my work") 

是不是有一些暈?你可以這么理解,當(dāng)帶參數(shù)的裝飾器被打在某個(gè)函數(shù)上時(shí),比如@logging(level='DEBUG'),它其實(shí)是一個(gè)函數(shù),會(huì)馬上被執(zhí)行,只要這個(gè)它返回的結(jié)果是一個(gè)裝飾器時(shí),那就沒問題。細(xì)細(xì)再體會(huì)一下。

基于類實(shí)現(xiàn)的裝飾器

裝飾器函數(shù)其實(shí)是這樣一個(gè)接口約束,它必須接受一個(gè)callable對象作為參數(shù),然后返回一個(gè)callable對象。在Python中一般callable對象都是函數(shù),但也有例外。只要某個(gè)對象重載了__call__()方法,那么這個(gè)對象就是callable的。

 
 
  1. class Test():
  2.     def __call__(self):
  3.         print 'call me!'
  4. t = Test()
  5. t()  # call me 

像__call__這樣前后都帶下劃線的方法在Python中被稱為內(nèi)置方法,有時(shí)候也被稱為魔法方法。重載這些魔法方法一般會(huì)改變對象的內(nèi)部行為。上面這個(gè)例子就讓一個(gè)類對象擁有了被調(diào)用的行為。

回到裝飾器上的概念上來,裝飾器要求接受一個(gè)callable對象,并返回一個(gè)callable對象(不太嚴(yán)謹(jǐn),詳見后文)。那么用類來實(shí)現(xiàn)也是也可以的。我們可以讓類的構(gòu)造函數(shù)__init__()接受一個(gè)函數(shù),然后重載__call__()并返回一個(gè)函數(shù),也可以達(dá)到裝飾器函數(shù)的效果。

 
 
  1. class logging(object):
  2.     def __init__(self, func):
  3.         self.func = func
  4.     def __call__(self, *args, **kwargs):
  5.         print "[DEBUG]: enter function {func}()".format(
  6.             func=self.func.__name__)
  7.         return self.func(*args, **kwargs)
  8. @logging
  9. def say(something):
  10.     print "say {}!".format(something) 

帶參數(shù)的類裝飾器

如果需要通過類形式實(shí)現(xiàn)帶參數(shù)的裝飾器,那么會(huì)比前面的例子稍微復(fù)雜一點(diǎn)。那么在構(gòu)造函數(shù)里接受的就不是一個(gè)函數(shù),而是傳入的參數(shù)。通過類把這些參數(shù)保存起來。然后在重載__call__方法是就需要接受一個(gè)函數(shù)并返回一個(gè)函數(shù)。

 
 
  1. class logging(object):
  2.     def __init__(self, level='INFO'):
  3.         self.level = level
  4.         
  5.     def __call__(self, func): # 接受函數(shù)
  6.         def wrapper(*args, **kwargs):
  7.             print "[{level}]: enter function {func}()".format(
  8.                 level=self.level,
  9.                 func=func.__name__)
  10.             func(*args, **kwargs)
  11.         return wrapper  #返回函數(shù)
  12. @logging(level='INFO')
  13. def say(something):
  14.     print "say {}!".format(something) 

內(nèi)置的裝飾器

內(nèi)置的裝飾器和普通的裝飾器原理是一樣的,只不過返回的不是函數(shù),而是類對象,所以更難理解一些。

@property

在了解這個(gè)裝飾器前,你需要知道在不使用裝飾器怎么寫一個(gè)屬性。

 
 
  1. def getx(self):
  2.     return self._x
  3. def setx(self, value):
  4.     self._x = value
  5.     
  6. def delx(self):
  7.    del self._x
  8. # create a property
  9. x = property(getx, setx, delx, "I am doc for x property")

以上就是一個(gè)Python屬性的標(biāo)準(zhǔn)寫法,其實(shí)和Java挺像的,但是太羅嗦。有了@語法糖,能達(dá)到一樣的效果但看起來更簡單。

 
 
  1. @property
  2. def x(self): ...
  3. # 等同于
  4. def x(self): ...
  5. x = property(x) 

屬性有三個(gè)裝飾器:setter, getter, deleter ,都是在property()的基礎(chǔ)上做了一些封裝,因?yàn)閟etter和deleter是property()的第二和第三個(gè)參數(shù),不能直接套用@語法。getter裝飾器和不帶getter的屬性裝飾器效果是一樣的,估計(jì)只是為了湊數(shù),本身沒有任何存在的意義。經(jīng)過@property裝飾過的函數(shù)返回的不再是一個(gè)函數(shù),而是一個(gè)property對象。

 
 
  1. >>> property()
  2.  

@staticmethod,@classmethod

有了@property裝飾器的了解,這兩個(gè)裝飾器的原理是差不多的。@staticmethod返回的是一個(gè)staticmethod類對象,而@classmethod返回的是一個(gè)classmethod類對象。他們都是調(diào)用的是各自的__init__()構(gòu)造函數(shù)。

 
 
  1. class classmethod(object):
  2.     """
  3.     classmethod(function) -> method
  4.     """    
  5.     def __init__(self, function): # for @classmethod decorator
  6.         pass
  7.     # ...
  8. class staticmethod(object):
  9.     """
  10.     staticmethod(function) -> method
  11.     """
  12.     def __init__(self, function): # for @staticmethod decorator
  13.         pass
  14.     # ... 

裝飾器的@語法就等同調(diào)用了這兩個(gè)類的構(gòu)造函數(shù)。

 
 
  1. class Foo(object):
  2.     @staticmethod
  3.     def bar():
  4.         pass
  5.     
  6.     # 等同于 bar = staticmethod(bar) 

至此,我們上文提到的裝飾器接口定義可以更加明確一些,裝飾器必須接受一個(gè)callable對象,其實(shí)它并不關(guān)心你返回什么,可以是另外一個(gè)callable對象(大部分情況),也可以是其他類對象,比如property。

裝飾器里的那些坑

裝飾器可以讓你代碼更加優(yōu)雅,減少重復(fù),但也不全是優(yōu)點(diǎn),也會(huì)帶來一些問題。

位置錯(cuò)誤的代碼

讓我們直接看示例代碼。

 
 
  1. def html_tags(tag_name):
  2.     print 'begin outer function.'
  3.     def wrapper_(func):
  4.         print "begin of inner wrapper function."
  5.         def wrapper(*args, **kwargs):
  6.             content = func(*args, **kwargs)
  7.             print "<{tag}>{content}".format(tag=tag_name, content=content)
  8.         print 'end of inner wrapper function.'
  9.         return wrapper
  10.     print 'end of outer function'
  11.     return wrapper_
  12. @html_tags('b')
  13. def hello(name='Toby'):
  14.     return 'Hello {}!'.format(name)
  15. hello()
  16. hello() 

在裝飾器中我在各個(gè)可能的位置都加上了print語句,用于記錄被調(diào)用的情況。你知道他們***打印出來的順序嗎?如果你心里沒底,那么***不要在裝飾器函數(shù)之外添加邏輯功能,否則這個(gè)裝飾器就不受你控制了。以下是輸出結(jié)果:

 
 
  1. begin outer function.
  2. end of outer function
  3. begin of inner wrapper function.
  4. end of inner wrapper function.
  5. Hello Toby!
  6. Hello Toby! 

錯(cuò)誤的函數(shù)簽名和文檔

裝飾器裝飾過的函數(shù)看上去名字沒變,其實(shí)已經(jīng)變了。

 
 
  1. def logging(func):
  2.     def wrapper(*args, **kwargs):
  3.         """print log before a function."""
  4.         print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
  5.         return func(*args, **kwargs)
  6.     return wrapper
  7. @logging
  8. def say(something):
  9.     """say something"""
  10.     print "say {}!".format(something)
  11. print say.__name__  # wrapper 

為什么會(huì)這樣呢?只要你想想裝飾器的語法糖@代替的東西就明白了。@等同于這樣的寫法。

 
 
  1. say = logging(say)

logging其實(shí)返回的函數(shù)名字剛好是wrapper,那么上面的這個(gè)語句剛好就是把這個(gè)結(jié)果賦值給say,say的__name__自然也就是wrapper了,不僅僅是name,其他屬性也都是來自wrapper,比如doc,source等等。

使用標(biāo)準(zhǔn)庫里的functools.wraps,可以基本解決這個(gè)問題。

 
 
  1. from functools import wraps
  2. def logging(func):
  3.     @wraps(func)
  4.     def wrapper(*args, **kwargs):
  5.         """print log before a function."""
  6.         print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
  7.         return func(*args, **kwargs)
  8.     return wrapper
  9. @logging
  10. def say(something):
  11.     """say something"""
  12.     print "say {}!".format(something)
  13. print say.__name__  # say
  14. print say.__doc__ # say something 

看上去不錯(cuò)!主要問題解決了,但其實(shí)還不太***。因?yàn)楹瘮?shù)的簽名和源碼還是拿不到的。

 
 
  1. import inspect
  2. print inspect.getargspec(say)  # failed
  3. print inspect.getsource(say)  # failed 

如果要徹底解決這個(gè)問題可以借用第三方包,比如wrapt。后文有介紹。

不能裝飾@staticmethod 或者 @classmethod

當(dāng)你想把裝飾器用在一個(gè)靜態(tài)方法或者類方法時(shí),不好意思,報(bào)錯(cuò)了。

 
 
  1. class Car(object):
  2.     def __init__(self, model):
  3.         self.model = model
  4.     @logging  # 裝飾實(shí)例方法,OK
  5.     def run(self):
  6.         print "{} is running!".format(self.model)
  7.     @logging  # 裝飾靜態(tài)方法,F(xiàn)ailed
  8.     @staticmethod
  9.     def check_model_for(obj):
  10.         if isinstance(obj, Car):
  11.             print "The model of your car is {}".format(obj.model)
  12.         else:
  13.             print "{} is not a car!".format(obj)
  14. """
  15. Traceback (most recent call last):
  16. ...
  17.   File "example_4.py", line 10, in logging
  18.     @wraps(func)
  19.   File "C:\Python27\lib\functools.py", line 33, in update_wrapper
  20.     setattr(wrapper, attr, getattr(wrapped, attr))
  21. AttributeError: 'staticmethod' object has no attribute '__module__'
  22. """ 

前面已經(jīng)解釋了@staticmethod這個(gè)裝飾器,其實(shí)它返回的并不是一個(gè)callable對象,而是一個(gè)staticmethod對象,那么它是不符合裝飾器要求的(比如傳入一個(gè)callable對象),你自然不能在它之上再加別的裝飾器。要解決這個(gè)問題很簡單,只要把你的裝飾器放在@staticmethod之前就好了,因?yàn)槟愕难b飾器返回的還是一個(gè)正常的函數(shù),然后再加上一個(gè)@staticmethod是不會(huì)出問題的。

 
 
  1. class Car(object):
  2.     def __init__(self, model):
  3.         self.model = model
  4.     @staticmethod
  5.     @logging  # 在@staticmethod之前裝飾,OK
  6.     def check_model_for(obj):
  7.         pass 

如何優(yōu)化你的裝飾器

嵌套的裝飾函數(shù)不太直觀,我們可以使用第三方包類改進(jìn)這樣的情況,讓裝飾器函數(shù)可讀性更好。

decorator.py

decorator.py 是一個(gè)非常簡單的裝飾器加強(qiáng)包。你可以很直觀的先定義包裝函數(shù)wrapper(),再使用decorate(func, wrapper)方法就可以完成一個(gè)裝飾器。

 
 
  1. from decorator import decorate
  2. def wrapper(func, *args, **kwargs):
  3.     """print log before a function."""
  4.     print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
  5.     return func(*args, **kwargs)
  6. def logging(func):
  7.     return decorate(func, wrapper)  # 用wrapper裝飾func 

你也可以使用它自帶的@decorator裝飾器來完成你的裝飾器。

 
 
  1. from decorator import decorator
  2. @decorator
  3. def logging(func, *args, **kwargs):
  4.     print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
  5.     return func(*args, **kwargs) 

decorator.py實(shí)現(xiàn)的裝飾器能完整保留原函數(shù)的name,doc和args,唯一有問題的就是inspect.getsource(func)返回的還是裝飾器的源代碼,你需要改成inspect.getsource(func.__wrapped__)。

wrapt

wrapt是一個(gè)功能非常完善的包,用于實(shí)現(xiàn)各種你想到或者你沒想到的裝飾器。使用wrapt實(shí)現(xiàn)的裝飾器你不需要擔(dān)心之前inspect中遇到的所有問題,因?yàn)樗紟湍闾幚砹耍踔羒nspect.getsource(func)也準(zhǔn)確無誤。

 
 
  1. import wrapt
  2. # without argument in decorator
  3. @wrapt.decorator
  4. def logging(wrapped, instance, args, kwargs):  # instance is must
  5.     print "[DEBUG]: enter {}()".format(wrapped.__name__)
  6.     return wrapped(*args, **kwargs)
  7. @logging
  8. def say(something): pass 

使用wrapt你只需要定義一個(gè)裝飾器函數(shù),但是函數(shù)簽名是固定的,必須是(wrapped, instance, args, kwargs),注意第二個(gè)參數(shù)instance是必須的,就算你不用它。當(dāng)裝飾器裝飾在不同位置時(shí)它將得到不同的值,比如裝飾在類實(shí)例方法時(shí)你可以拿到這個(gè)類實(shí)例。根據(jù)instance的值你能夠更加靈活的調(diào)整你的裝飾器。另外,args和kwargs也是固定的,注意前面沒有星號。在裝飾器內(nèi)部調(diào)用原函數(shù)時(shí)才帶星號。

如果你需要使用wrapt寫一個(gè)帶參數(shù)的裝飾器,可以這樣寫。

 
 
  1. def logging(level):
  2.     @wrapt.decorator
  3.     def wrapper(wrapped, instance, args, kwargs):
  4.         print "[{}]: enter {}()".format(level, wrapped.__name__)
  5.         return wrapped(*args, **kwargs)
  6.     return wrapper
  7. @logging(level="INFO")
  8. def do(work): pass 

關(guān)于wrapt的使用,建議查閱官方文檔,在此不在贅述。

  • http://wrapt.readthedocs.io/e...

小結(jié)

Python的裝飾器和Java的注解(Annotation)并不是同一回事,和C#中的特性(Attribute)也不一樣,完全是兩個(gè)概念。

裝飾器的理念是對原函數(shù)、對象的加強(qiáng),相當(dāng)于重新封裝,所以一般裝飾器函數(shù)都被命名為wrapper(),意義在于包裝。函數(shù)只有在被調(diào)用時(shí)才會(huì)發(fā)揮其作用。比如@logging裝飾器可以在函數(shù)執(zhí)行時(shí)額外輸出日志,@cache裝飾過的函數(shù)可以緩存計(jì)算結(jié)果等等。

而注解和特性則是對目標(biāo)函數(shù)或?qū)ο筇砑右恍傩裕喈?dāng)于將其分類。這些屬性可以通過反射拿到,在程序運(yùn)行時(shí)對不同的特性函數(shù)或?qū)ο蠹右愿深A(yù)。比如帶有Setup的函數(shù)就當(dāng)成準(zhǔn)備步驟執(zhí)行,或者找到所有帶有TestMethod的函數(shù)依次執(zhí)行等等。

至此我所了解的裝飾器已經(jīng)講完,但是還有一些內(nèi)容沒有提到,比如裝飾類的裝飾器。有機(jī)會(huì)再補(bǔ)充。謝謝觀看。


本文題目:詳解Python的裝飾器
URL標(biāo)題:http://m.5511xx.com/article/dpjigdi.html