Python裝飾器(@wraps)及閉包用例詳解


一、裝飾器簡介

1. 裝飾器是什么?

概括地講,裝飾器的作用就是在不修改被裝飾對象源代碼和調用方式的前提下為被裝飾對象添加額外的功能

裝飾器經常用於比如:插入日志、性能測試、事務處理、緩存、權限校驗等場景。

有了裝飾器,就可以抽離出大量與函數功能本身無關的雷同代碼並繼續重用

2. 為什么用裝飾器?

軟件的設計應該遵循開放封閉原則,即對擴展是開放的,而對修改是封閉的。

在軟件設計完成后,不想改部分源碼又想添加新功能,就用到了裝飾器。

二、裝飾器的實現

函數裝飾器分為:無參裝飾器和有參裝飾兩種,二者的實現原理都是“函數嵌套+閉包+函數對象”的組合使用。

1. 閉包

閉包(Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數

這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。

def print_msg():  # print_msg是外圍函數
    msg = "I'm closure"

    def printer():  # printer是嵌套函數
        print(msg)
    return printer

closure = print_msg()  # 這里獲得的就是一個閉包
closure()  # 輸出 I'm closure

msg是一個局部變量,在print_msg函數執行之后就不會存在了。

但是嵌套函數引用了這個變量,將這個局部變量封閉在了嵌套函數中,這樣就形成了一個閉包。

2. 裝飾器語法糖

語法糖(Syntactic sugar),也譯為糖衣語法,指計算機語言中添加的某種語法。

這種語法對語言的功能並沒有影響,但是更方便程序員使用。

通常來說使用語法糖能夠增加程序的可讀性,從而減少程序代碼出錯的機會。

@ 符號是裝飾器的語法糖。它放在一個函數開始定義的地方(頭頂),和這個函數綁定在一起。

在我們調用這個函數的時候,會先將這個函數做為參數傳入它頭頂,即裝飾器里。

3. 時間計時器

以下用裝飾器來實現計算一個函數的執行時長,讓函數睡眠3秒。

# 這是裝飾函數
def timer(func):
    def wrapper(*args, **kw):
        start_time = time.time()
        func(*args, **kw)  # 這是函數真正執行的地方
        stop_time = time.time()
        cost_time = stop_time - start_time
        print("花費時間:{}秒".format(cost_time))
    return wrapper

import time

@timer
def want_sleep(sleep_time):
    time.sleep(sleep_time)

want_sleep(3)

4. 裝飾器中@wraps作用

裝飾器(decorator)在實現的時候,被裝飾后的函數其實已經是另外一個函數了(函數名等函數屬性會發生改變)。

為了不影響,Python的functools包中提供了一個叫wraps的裝飾器來消除這樣的副作用。

寫一個裝飾器的時候,最好在實現之前加上functools中的wraps,它能保留原有函數的名稱和文檔字符串(DocStrings)。

文檔字符串用於解釋文檔程序,幫助程序文檔更加簡單易懂。

可以在函數體的第一行使用一對三個單引號 ‘’’ 或者一對三個雙引號 “”" 來定義文檔字符串。

使用 doc(注意雙下划線)調用函數中的文檔字符串屬性。

  1. 不使用@wraps裝飾器
def decorator(func):
    """this is decorator __doc__"""

    def wrapper(*args, **kwargs):
        """this is wrapper __doc__"""
        print("this is wrapper method")
        return func(*args, **kwargs)
    return wrapper

@decorator
def test():
    """this is test __doc__"""
    print("this is test method")

print("__name__: ", test.__name__)
print("__doc__: ", test.__doc__)

運行結果:
name: wrapper
doc: this is wrapper doc

分析:
對test()方法進行裝飾時候,實際上是

test = decorator(test)

返回的是wrapper方法的引用,也就是讓test指向了wrapper方法,所以調用test.name, 實際上是wrapper.name

這造成后面查找該方法的名字和注釋時得到裝飾器內嵌函數的名字和注釋

  1. 使用@wraps裝飾器解決這個問題
from functools import wraps


def decorator(func):
    """this is decorator __doc__"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        """this is wrapper __doc__"""
        print("this is wrapper method")
        return func(*args, **kwargs)
    return wrapper

@decorator
def test():
    """this is test __doc__"""
    print("this is test method")

print("__name__: ", test.__name__)
print("__doc__: ", test.__doc__)

運行結果:
name: test
doc: this is test doc

5. 裝飾器順序

一個函數可以同時定義多個裝飾器,比如:

@a
@b
@c
def f ():
    pass

它的執行順序是從里到外,最先調用最里層的裝飾器,最后調用最外層的裝飾器,它等效於:

f = a(b(c(f)))

三、裝飾器的參數

1. 無參類裝飾器

裝飾器不僅可以是函數,還可以是類。

基於類裝飾器的實現,必須實現 call 和 __init__兩個內置函數。

當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。

init :接收被裝飾函數
call :實現裝飾邏輯。

class logger(object):
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        print("[INFO]: the function {func}() is running..." \
              .format(func=self.func.__name__))
        return self.func(*args, **kwargs)
        
@logger
def say(something):
    print("say {}!".format(something))
say("hello")

運行結果:
[INFO]: the function say() is running…
say hello!

2. 有參類裝飾器

上面不帶參數的例子只能打印INFO級別的日志。

當需要打印DEBUG、WARNING等級別的日志時就需要給類裝飾器傳入參數,給這個函數指定級別了。

帶參數和不帶參數的類裝飾器有很大的不同。

init :不再接收被裝飾函數,而是接收傳入參數。
call :接收被裝飾函數,實現裝飾邏輯。

class logger(object):
  def __init__(self, level='INFO'):
    self.level = level

  def __call__(self, func): # 接受函數
    def wrapper(*args, **kwargs):
      print("[{level}]: the function {func}() is running..."\
        .format(level=self.level, func=func.__name__))
      func(*args, **kwargs)
    return wrapper #返回函數

@logger(level='WARNING')
def say(something):
  print("say {}!".format(something))

say("hello")

指定WARNING級別后的運行結果:
[WARNING]: the function say() is running…
say hello!

3. 無參裝飾器模板

其中wrapper功能:

1、調用原函數
2、為其增加新功能

def template(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
        
    return wrapper

4. 有參裝飾器模板

def 有參裝飾器(x,y,z):
    def outter(func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res
        return wrapper
    return outter

@有參裝飾器(1,y=2,z=3)
def 被裝飾對象():
    pass

結語

以上是Python裝飾器詳解,希望對大家有所幫助。如果大家有任何疑問請給我留言,我會盡快回復大家。在此也非常感謝大家對CSDN的支持!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM