成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

詳解Python的裝飾器

DandJ / 2365人閱讀

摘要:概括的講,裝飾器的作用就是為已經(jīng)存在的函數(shù)或?qū)ο筇砑宇~外的功能。在理解這些裝飾器之前,最好對函數(shù)的閉包和裝飾器的接口約定有一定了解。是一個非常簡單的裝飾器加強包。

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

為什么需要裝飾器

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

def say_hello():
    print "hello!"
    
def say_goodbye():
    print "hello!"  # bug here

if __name__ == "__main__":
    say_hello()
    say_goodbye()

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

[DEBUG]: Enter say_hello()
Hello!
[DEBUG]: Enter say_goodbye()
Goodbye!

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

def say_hello():
    print "[DEBUG]: enter say_hello()"
    print "hello!"

def say_goodbye():
    print "[DEBUG]: enter say_goodbye()"
    print "hello!"

if __name__ == "__main__":
    say_hello()
    say_goodbye()

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

def debug():
    import inspect
    caller_name = inspect.stack()[1][3]
    print "[DEBUG]: enter {}()".format(caller_name)   

def say_hello():
    debug()
    print "hello!"

def say_goodbye():
    debug()
    print "goodbye!"

if __name__ == "__main__":
    say_hello()
    say_goodbye()

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

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

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

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

怎么寫一個裝飾器

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

def debug(func):
    def wrapper():
        print "[DEBUG]: enter {}()".format(func.__name__)
        return func()
    return wrapper

def say_hello():
    print "hello!"

say_hello = debug(say_hello)  # 添加功能并保持原函數(shù)名不變

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

def debug(func):
    def wrapper():
        print "[DEBUG]: enter {}()".format(func.__name__)
        return func()
    return wrapper

@debug
def say_hello():
    print "hello!"

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

def debug(func):
    def wrapper(something):  # 指定一毛一樣的參數(shù)
        print "[DEBUG]: enter {}()".format(func.__name__)
        return func(something)
    return wrapper  # 返回包裝過函數(shù)

@debug
def say(something):
    print "hello {}!".format(something)

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

def debug(func):
    def wrapper(*args, **kwargs):  # 指定宇宙無敵參數(shù)
        print "[DEBUG]: enter {}()".format(func.__name__)
        print "Prepare and say...",
        return func(*args, **kwargs)
    return wrapper  # 返回

@debug
def say(something):
    print "hello {}!".format(something)

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

高級一點的裝飾器

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

帶參數(shù)的裝飾器

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

def logging(level):
    def wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print "[{level}]: enter function {func}()".format(
                level=level,
                func=func.__name__)
            return func(*args, **kwargs)
        return inner_wrapper
    return wrapper

@logging(level="INFO")
def say(something):
    print "say {}!".format(something)

# 如果沒有使用@語法,等同于
# say = logging(level="INFO")(say)

@logging(level="DEBUG")
def do(something):
    print "do {}...".format(something)

if __name__ == "__main__":
    say("hello")
    do("my work")

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

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

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

class Test():
    def __call__(self):
        print "call me!"

t = Test()
t()  # call me

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

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

class logging(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print "[DEBUG]: enter function {func}()".format(
            func=self.func.__name__)
        return self.func(*args, **kwargs)
@logging
def say(something):
    print "say {}!".format(something)
帶參數(shù)的類裝飾器

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

class logging(object):
    def __init__(self, level="INFO"):
        self.level = level
        
    def __call__(self, func): # 接受函數(shù)
        def wrapper(*args, **kwargs):
            print "[{level}]: enter function {func}()".format(
                level=self.level,
                func=func.__name__)
            func(*args, **kwargs)
        return wrapper  #返回函數(shù)

@logging(level="INFO")
def say(something):
    print "say {}!".format(something)
內(nèi)置的裝飾器

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

@property

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

def getx(self):
    return self._x

def setx(self, value):
    self._x = value
    
def delx(self):
   del self._x

# create a property
x = property(getx, setx, delx, "I am doc for x property")

以上就是一個Python屬性的標準寫法,其實和Java挺像的,但是太羅嗦。有了@語法糖,能達到一樣的效果但看起來更簡單。

@property
def x(self): ...

# 等同于

def x(self): ...
x = property(x)

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

>>> property()
@staticmethod,@classmethod

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

class classmethod(object):
    """
    classmethod(function) -> method
    """    
    def __init__(self, function): # for @classmethod decorator
        pass
    # ...
class staticmethod(object):
    """
    staticmethod(function) -> method
    """
    def __init__(self, function): # for @staticmethod decorator
        pass
    # ...

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

class Foo(object):

    @staticmethod
    def bar():
        pass
    
    # 等同于 bar = staticmethod(bar)

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

裝飾器里的那些坑

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

位置錯誤的代碼

讓我們直接看示例代碼。

def html_tags(tag_name):
    print "begin outer function."
    def wrapper_(func):
        print "begin of inner wrapper function."
        def wrapper(*args, **kwargs):
            content = func(*args, **kwargs)
            print "<{tag}>{content}".format(tag=tag_name, content=content)
        print "end of inner wrapper function."
        return wrapper
    print "end of outer function"
    return wrapper_

@html_tags("b")
def hello(name="Toby"):
    return "Hello {}!".format(name)

hello()
hello()

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

begin outer function.
end of outer function
begin of inner wrapper function.
end of inner wrapper function.
Hello Toby!
Hello Toby!
錯誤的函數(shù)簽名和文檔

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

def logging(func):
    def wrapper(*args, **kwargs):
        """print log before a function."""
        print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
        return func(*args, **kwargs)
    return wrapper

@logging
def say(something):
    """say something"""
    print "say {}!".format(something)

print say.__name__  # wrapper

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

say = logging(say)

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

使用標準庫里的functools.wraps,可以基本解決這個問題。

from functools import wraps

def logging(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """print log before a function."""
        print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
        return func(*args, **kwargs)
    return wrapper

@logging
def say(something):
    """say something"""
    print "say {}!".format(something)

print say.__name__  # say
print say.__doc__ # say something

看上去不錯!主要問題解決了,但其實還不太完美。因為函數(shù)的簽名和源碼還是拿不到的。

import inspect
print inspect.getargspec(say)  # failed
print inspect.getsource(say)  # failed

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

不能裝飾@staticmethod 或者 @classmethod

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

class Car(object):
    def __init__(self, model):
        self.model = model

    @logging  # 裝飾實例方法,OK
    def run(self):
        print "{} is running!".format(self.model)

    @logging  # 裝飾靜態(tài)方法,F(xiàn)ailed
    @staticmethod
    def check_model_for(obj):
        if isinstance(obj, Car):
            print "The model of your car is {}".format(obj.model)
        else:
            print "{} is not a car!".format(obj)

"""
Traceback (most recent call last):
...
  File "example_4.py", line 10, in logging
    @wraps(func)
  File "C:Python27libfunctools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: "staticmethod" object has no attribute "__module__"
"""

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

class Car(object):
    def __init__(self, model):
        self.model = model

    @staticmethod
    @logging  # 在@staticmethod之前裝飾,OK
    def check_model_for(obj):
        pass
如何優(yōu)化你的裝飾器

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

decorator.py

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

from decorator import decorate

def wrapper(func, *args, **kwargs):
    """print log before a function."""
    print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
    return func(*args, **kwargs)

def logging(func):
    return decorate(func, wrapper)  # 用wrapper裝飾func

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

from decorator import decorator

@decorator
def logging(func, *args, **kwargs):
    print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
    return func(*args, **kwargs)

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

wrapt

wrapt是一個功能非常完善的包,用于實現(xiàn)各種你想到或者你沒想到的裝飾器。使用wrapt實現(xiàn)的裝飾器你不需要擔(dān)心之前inspect中遇到的所有問題,因為它都幫你處理了,甚至inspect.getsource(func)也準確無誤。

import wrapt

# without argument in decorator
@wrapt.decorator
def logging(wrapped, instance, args, kwargs):  # instance is must
    print "[DEBUG]: enter {}()".format(wrapped.__name__)
    return wrapped(*args, **kwargs)

@logging
def say(something): pass

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

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

def logging(level):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print "[{}]: enter {}()".format(level, wrapped.__name__)
        return wrapped(*args, **kwargs)
    return wrapper

@logging(level="INFO")
def do(work): pass

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

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

小結(jié)

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

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

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

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

本文源碼 https://github.com/tobyqin/py...

關(guān)于作者:Python技術(shù)愛好者,目前從事測試開發(fā)相關(guān)工作,轉(zhuǎn)載請注明原文出處。

歡迎關(guān)注我的博客 http://betacat.online,你可以到我的公眾號中去當(dāng)吃瓜群眾。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/44230.html

相關(guān)文章

  • python裝飾詳解

    摘要:為了避免重復(fù)調(diào)用,可以適當(dāng)?shù)刈鼍彺妫难b飾器可以完美的完成這一任務(wù)。這意味著我們可以為方法創(chuàng)建裝飾器,只是要記得考慮。裝飾器封裝了函數(shù),這使得調(diào)試函數(shù)變得困難。另外,使用裝飾器去管理緩存和權(quán)限。 原文地址 之前用python簡單寫了一下斐波那契數(shù)列的遞歸實現(xiàn)(如下),發(fā)現(xiàn)運行速度很慢。 def fib_direct(n): assert n > 0, invalid n ...

    maybe_009 評論0 收藏0
  • Python裝飾另類用法

    摘要:今天我們一起探討一下裝飾器的另類用法。語法回顧開始之前我們再將裝飾器的語法回顧一下。例子本身只是演示了裝飾器的一種用法,但不是推薦你就這樣使用裝飾器。類裝飾器在以前,還不支持類裝飾器。 之前有比較系統(tǒng)介紹過Python的裝飾器(請查閱《詳解Python裝飾器》),本文算是一個補充。今天我們一起探討一下裝飾器的另類用法。 語法回顧 開始之前我們再將Python裝飾器的語法回顧一下。 @d...

    hqman 評論0 收藏0
  • python中關(guān)于閉包用法詳解

      小編寫這篇文章的主要目的,主要是來給大家介紹,關(guān)于python中,相關(guān)語法問題的解答,比如在python,我們會遇到閉包和裝飾器不會用的情況,那么,下文就會來給大家做一個詳細的解答?! ?args與**kwarsg及閉包和裝飾器  過程  先理解閉包,再理解裝飾器,不要忘了不定長參數(shù) deffunc():   msg='111'   deffunc1():   print(ms...

    89542767 評論0 收藏0
  • MobX詳解(二):ES7 裝飾 decorator

    摘要:在學(xué)習(xí)裝飾器語法之前,需要先溫習(xí)一下的一些基礎(chǔ)知識。函數(shù)最后必須返回。使用時也很簡單,如下在方法前面加上,就是裝飾器語法。裝備了,攻擊更強了。職業(yè)的基本攻擊穿上了,移動速度更快了。 在學(xué)習(xí)ES7裝飾器語法之前,需要先溫習(xí)一下ES5的一些基礎(chǔ)知識。 假設(shè)有對象如下:(便于理解) var person = { name: TOM } 在ES5中,對象中的每個屬性都有一個特性值來描述...

    Keagan 評論0 收藏0
  • Python

    摘要:最近看前端都展開了幾場而我大知乎最熱語言還沒有相關(guān)。有關(guān)書籍的介紹,大部分截取自是官方介紹。但從開始,標準庫為我們提供了模塊,它提供了和兩個類,實現(xiàn)了對和的進一步抽象,對編寫線程池進程池提供了直接的支持。 《流暢的python》閱讀筆記 《流暢的python》是一本適合python進階的書, 里面介紹的基本都是高級的python用法. 對于初學(xué)python的人來說, 基礎(chǔ)大概也就夠用了...

    dailybird 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<