日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python基础教程:函数装饰器详解

發布時間:2025/3/20 python 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python基础教程:函数装饰器详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

誰可以作為裝飾器(可以將誰編寫成裝飾器):

  • 函數
  • 方法
  • 實現了__call__的可調用類
  • 裝飾器可以去裝飾誰(誰可以被裝飾):

  • 函數
  • 方法
  • 基礎:函數裝飾器的表現方式

    假如你已經定義了一個函數funcA(),在準備定義函數funcB()的時候,如果寫成下面的格式:

    @funcA def funcB():...

    表示用函數funcA()裝飾函數funcB()。當然,也可以認為是funcA包裝函數funcB。它等價于:

    def funcB():...funcB = funcA(funcB)

    也就是說,將函數funcB作為函數funcA的參數,funcA會重新返回另一個可調用的對象(比如函數)并賦值給funcB。

    所以,funcA要想作為函數裝飾器,需要接收函數作為參數,并且返回另一個可調用對象(如函數)。例如:

    def funcA(F):......return Callable

    注意,函數裝飾器返回的可調用對象并不一定是原始的函數F,可以是任意其它可調用對象,比如另一個函數。但最終,這個返回的可調用對象都會被賦值給被裝飾的函數變量(上例中的funcB)。

    函數可以同時被多個裝飾器裝飾,后面的裝飾器以前面的裝飾器處理結果為基礎進行處理:

    ''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' @decorator1 @decorator2 def func():...# 等價于 func = decorator1(decorator2(func))

    當調用被裝飾后的funcB時,將自動將funcB進行裝飾,并調用裝飾后的對象。所以,下面是等價的調用方式:

    funcB() # 調用裝飾后的funcB funcA(funcB)()

    了解完函數裝飾器的表現后,大概也能猜到了,裝飾器函數可以用來擴展、增強另外一個函數。實際上,內置函數中staticmethod()、classmethod()和property()都是裝飾器函數,可以用來裝飾其它函數,在后面會學到它們的用法。

    兩個簡單的例子

    例如,函數f()返回一些字符串,現在要將它的返回結果轉換為大寫字母。可以定義一個函數裝飾器來增強函數f()。

    def toupper(func):def wrapper(*args, **kwargs):result = func(*args, **kwargs)return result.upper()return wrapper@toupper def f(x: str): # 等價于f = toupper(f)return xres = f("abcd") print(res)

    上面toupper()裝飾f()后,調用f("abcd")的時候,等價于執行toupper(f)("abcd"),參數"abcd"傳遞給裝飾器中的wrapper()中的*args,在wrapper中又執行了f("abcd"),使得原本屬于f()的整個過程都完整了,最后返回result.upper(),這部分是對函數f()的擴展部分。

    注意,上面的封裝函數wrapper()中使用了*args **kwargs,是為了確保任意參數的函數都能正確執行下去。

    再比如要計算一個函數autodown()的執行時長,可以額外定義一個函數裝飾器timecount()。

    ''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' import time# 函數裝飾器 def timecount(func):def wrapper(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(func.__name__, end - start)return resultreturn wrapper# 裝飾函數 @timecount def autodown(n: int):while n > 0:n -= 1# 調用被裝飾的函數 autodown(100000) autodown(1000000) autodown(10000000)

    執行結果:

    autodown 0.004986763000488281 autodown 0.05684685707092285 autodown 0.5336081981658936

    上面wrapper()中的return是多余的,是因為這里裝飾的autodown()函數自身沒有返回值。但卻不應該省略這個return,因為timecount()可以去裝飾其它可能有返回值的函數。

    @functools.wraps

    前面的裝飾器代碼邏輯上沒有什么問題,但是卻存在隱藏的問題:函數的元數據信息丟了。比如doc、注解等。

    比如下面的代碼:

    @timecount def autodown(n: int):''' some docs '''while n > 0:n -= 1print(autodown.__name__) print(autodown.__doc__) print(autodown.__annotations__)

    執行結果為:

    wrapper None {}

    所以,必須要將被裝飾函數的元數據保留下來??梢允褂胒unctools模塊中的wraps()裝飾一下裝飾器中的wrapper()函數。如下:

    ''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' import time from functools import wrapsdef timecount(func):@wraps(func)def wrapper(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(func.__name__, end - start)return resultreturn wrapper

    現在,再去查看autodown函數的元數據信息,將會得到被保留下來的內容:

    autodownsome doc {'n': <class 'int'>}

    所以,wraps()的簡單用法是:向wraps()中傳遞的func參數,那么func的元數據就會被保留下來。

    上面@wraps(func)裝飾wrapper的過程等價于:

    def wrapper(*args, **kwargs):... wrapper = wraps(func)(wrapper)

    請注意這一點,因為在將類作為裝飾器的時候,經常會在__init__(self, func)里這樣使用:

    class cls:def __init__(self, func):wraps(func)(self)...def __call__(self, *args, **kwargs):...

    解除裝飾

    函數被裝飾后,如何再去訪問未被裝飾狀態下的這個函數?@wraps還有一個重要的特性,可以通過被裝飾對象的__wrapped__屬性來直接訪問被裝飾對象。例如:

    autodown.__wrapped__(1000000)new_autodown = autodown.__wrapped__ new_autodown(1000000)

    上面的調用不會去調用裝飾后的函數,所以不會輸出執行時長。

    注意,如果函數被多個裝飾器裝飾,那么通過__wrapped__,將只會解除第一個裝飾過程。例如:

    @decorator1 @decorator2 @decorator3 def f():...

    當訪問f.__wrapped__()的時候,只有decorator1被解除,剩余的所有裝飾器仍然有效。注意,python 3.3之前是略過所有裝飾器。

    下面是一個多裝飾的示例:

    ''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' from functools import wrapsdef decorator1(func):@wraps(func)def wrapper(*args, **kwargs):print("in decorator1")return func(*args, **kwargs)return wrapperdef decorator2(func):@wraps(func)def wrapper(*args, **kwargs):print("in decorator2")return func(*args, **kwargs)return wrapperdef decorator3(func):@wraps(func)def wrapper(*args, **kwargs):print("in decorator3")return func(*args, **kwargs)return wrapper@decorator1 @decorator2 @decorator3 def addNum(x, y):return x+y

    返回結果:

    in decorator1 in decorator2 in decorator3 5 in decorator2 in decorator3 5

    如果不使用functools的@wraps的__wrapped__,想要手動去引用原始函數,需要做的工作可能會非常多。所以,如有需要,直接使用__wrapped__去調用未被裝飾的函數比較好。

    另外,并不是所有裝飾器中都使用了@wraps。

    帶參數的函數裝飾器

    函數裝飾器也是可以帶上參數的。

    @decorator(x,y,z) def func():...

    它等價于:

    func = decorator(x,y,z)(func)

    它并不是"天生"就這樣等價的,而是根據編碼規范編寫裝飾器的時候,通常會這樣。其實帶參數的函數裝飾器寫起來有點繞:先定義一個帶有參數的外層函數,它是外在的函數裝飾器,這個函數內包含了真正的裝飾器函數,而這個內部的函數裝飾器的內部又包含了被裝飾的函數封裝。也就是函數嵌套了一次又一次。

    所以,結構大概是這樣的:

    def out_decorator(some_args):...SOME CODE...def real_decorator(func):...SOME CODE...def wrapper(*args, **kwargs):...SOME CODE WITH func...return wrapperreturn real_decorator# 等價于func = out_decorator(some_args)(func) @out_decorator(some_args) def func():...

    下面是一個簡單的例子:

    from functools import wrapsdef out_decorator(x, y, z):def decorator(func):@wraps(func)def wrapper(*args, **kwargs):print(x)print(y)print(z)return func(*args, **kwargs)return wrapperreturn decorator@out_decorator("xx", "yy", "zz") def addNum(x, y):return x+yprint(addNum(2, 3))

    參數隨意的裝飾器

    根據前面介紹的兩種情況,裝飾器可以帶參數、不帶參數,所以有兩種裝飾的方式,要么是下面的(1),要么是下面的(2)。

    @decorator # (1) @decorator(x,y,z) # (2)

    所以,根據不同的裝飾方式,需要編寫是否帶參數的不同裝飾器。

    但是現在想要編寫一個將上面兩種參數方式統一起來的裝飾器。

    可能第一想法是讓裝飾器參數默認化:

    def out_decorator(arg1=X, arg2=Y...):def decorator(func):@wraps(func)def wrapper(*args, **kwargs):...return wrapperreturn decorator

    現在可以用下面兩種方式來裝飾:

    @out_decorator() @out_decorator(arg1,arg2)

    雖然上面兩種裝飾方式會正確進行,但這并非合理做法,因為下面這種最通用的裝飾方式會錯誤:

    @out_decorator

    為了解決這個問題,回顧下前面裝飾器是如何等價的:

    # 等價于 func = decorator(func) @decorator def func():...# 等價于 func = out_decorator(x, y, z)(func) @out_decorator(x, y, z) def func():...

    上面第二種方式中,out_decorator(x,y,z)才是真正返回的內部裝飾器。所以,可以修改下裝飾器的編寫方式,將func也作為out_decorator()的其中一個參數:

    from functools import wraps,partialdef decorator(func=None, arg1=X, arg2=Y):# 如果func為None,說明觸發的帶參裝飾器# 直接返回partial()封裝后的裝飾器函數if func is None:decorator_new = partial(decorator, arg1=arg1, arg2=arg2)return decorator_new#return partial(decorator, arg1=arg1, arg2=arg2)# 下面是裝飾器的完整裝飾內容@wraps(func)def wrapper(*args, **kwargs):...return wrapper

    上面使用了functools模塊中的partial()函數,它可以返回一個新的將某些參數"凍結"后的函數,使得新的函數無需指定這些已被"凍結"的參數,從而減少參數的數量。

    現在,可以統一下面3種裝飾方式:

    @decorator() @decorator(arg1=x,arg2=y) @decorator

    前兩種裝飾方式,等價的調用方式是decorator()(func)和decorator(arg1=x,arg2=y)(func),它們的func都為None,所以都會通過partial()返回通常的裝飾方式@decorator所等價的形式。

    需要注意的是,因為上面的參數結構中包含了func=None作為第一個參數,所以帶參數裝飾時,必須使用keyword格式來傳遞參數,不能使用位置參數。

    下面是一個簡單的示例:

    from functools import wraps, partialdef decorator(func=None, x=1, y=2, z=3):if func is None:return partial(decorator, x=x, y=y, z=z)@wraps(func)def wrapper(*args, **kwargs):print("x: ", x)print("y: ", y)print("z: ", z)return func(*args, **kwargs)return wrapper

    下面3種裝飾方式都可以:

    @decorator def addNum(a, b):return a + b print(addNum(2, 3))print("=" * 40)@decorator() def addNum(a, b):return a + b print(addNum(2, 3))print("=" * 40)# 必須使用關鍵字參數進行裝飾 @decorator(x="xx", y="yy", z="zz") def addNum(a, b):return a + b print(addNum(2, 3))

    返回結果:

    x: 1 y: 2 z: 3 5 ==================== x: 1 y: 2 z: 3 5 ==================== x: xx y: yy z: zz 5

    總結

    以上是生活随笔為你收集整理的python基础教程:函数装饰器详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。