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

歡迎訪問 生活随笔!

生活随笔

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

python

python装饰器_python装饰器完全指南之一

發布時間:2025/3/21 python 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python装饰器_python装饰器完全指南之一 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

設我們有一組函數,它們有共同的錯誤處理方法,比如打印日志和記錄審計信息等。很顯然,在每一個函數中都重復這些邏輯是不恰當的,它們應該被提煉到一個函數里,在這個函數的保護下,再調用我們的業務邏輯處理功能。

盡管錯誤處理可能占據代碼的主要部分,但業務邏輯才是程序的核心價值。因此,從代碼結構上看,錯誤處理應該處于可被忽略的非中心地帶。如果我們每次調用業務邏輯處理功能前,都要先顯式地從一個錯誤處理函數開始,這種寫法顯然是頭重腳輕,也會打斷代碼閱讀者的思緒。基于這些原因,開發語言引入了面向切面的編程(AOP):把與主業務無關的事情,放到代碼之外去做。

裝飾器是AOP編程中不可缺少的語法糖。通過裝飾器語法,可以使得程序更簡潔易讀。本文對裝飾器的基礎原理、一般寫法、corner case和常見場景進行了探討。

三個系列共三篇文章,可能是互聯網上最全、最深入的python裝飾器指南之一。

從一個最簡單的裝飾器開始

假設我們有一個功能函數(從現在開始,我們把被裝飾器修飾,完成業務邏輯的那些函數稱作功能函數,以區別于裝飾器函數),出于調試目的,我們希望打印出它的參數及每次調用的返回值。

假設功能函數如下:

# block 1def buggy_incr_by(number): import random return random.randint(0,10) + number

我們可以定義這樣一個函數:

# block 2def snoop(func): def wrapper(number): print(f" >>> invoke {func.__name__} with parameter: {number}") result = func(number) return result print(f"<<< {func.__name__} returned {result}") return wrapper

現在,運用裝飾器語法:

# block 3@snoopdef buggy_incr_by(number): import random return random.randint(0,10) + number# call and check the resultbuggy_incr_by(3)# --- output --->>> invoke buggy_incr_by with parameter: 3<<< buggy_incr_by returned 13

裝飾器究竟是如何工作的?

現在我們來看一看這一切是如何發生的。

這里最基本的原理有:

1. 在python中,function(函數)也是一種對象(當不帶括號引用時)。你可以任意選擇一個函數f,通過dir(f)來查看它有哪些屬性。

2. 在函數內也可以定義函數,并返回這個定義的函數對象。這是因為根據原理1,函數本身也是對象。

3. 模塊加載器調用exec_module時,會查找和解析@語句,通過執行 func = decorator(func),重新定義功能函數。

在上面的例子中,我們定義了裝飾器函數snoop,它接受一個規定好的參數(必須),即功能函數對象本身。decorator的主要功能是定義并返回一個函數對象(下面稱之為替換函數)。這個函數對象中,完成我們需要的面向切面的功能,并且調用功能函數,返回其返回值。

當上述代碼所在的模塊文件被importlib加載并執行時,加載器(Loader)發現存在'@'語法糖,于是執行:

# block 4buggy_incr_by = snoop(buggy_incr_by)

結合snoop的代碼不難發現,snoop將返回一個名為wrapper的函數對象(替換函數),賦值給buggy_incr_by,所以此后調用buggy_incr_by,實際上就是在調用這個wrapper。

下面是寫一個最簡單的裝飾器時的一般要訣:

1. 裝飾器decorator只接受一個形參(名字可以任意取),這個形參將模塊加載器調用exec_module時,從@注解的下一行函數的定義中找到被定義的函數對象傳入。見上一個代碼塊的說明。

2. 裝飾器的函數體必須定義并返回一個wrapper函數(名字可以任意取)。這個wrapper(替換)函數的簽名一般情況下等同于功能函數。例外情況在下文中敘述。

3. 在添加裝飾器注解(即'@'語法)時,不需要顯式地將功能函數參數傳給裝飾器,這將由模塊加載器自動完成。因此,如果裝飾器只有這一個參數,注解中必須是不帶括號引用,見上面第2行。

4. 如果功能函數有返回值,則在wrapper的函數體中,也需要將返回值返回,參見block 2第6行。

通過上述分析,我們還有幾個重要的結論:

1. 裝飾器語法在模塊加載時就運行了,并且重新定義了功能函數的指向(即上述代碼中的wrapper)。

2. 在定義wrapper時,功能函數并沒有真正被調用,因此需要延遲綁定的參數,比如self對象,此時是不存在的。

3. 在代碼的其它地方調用功能函數時,實際上是在調用上述wrapper,此時實現參數的綁定(即給形參賦值)。

找回丟失的調試信息

從前面的分析可以看出,功能函數在模塊加載過程中,實際上被替換成了wrapper函數。我們可以通過下面的測試來發現這一點:

print(buggy_incr_by.__name__)# --output---wrapper

顯然buggy_incr_by已經被替換了。但這里也暴露出一個問題:如果程序出錯,則在需要顯示棧信息的地方,則都會顯示為wrapper,而不是功能函數的名字。比如下面一例:

def snoop(func): def wrapper(number): print("passed in param is ", number) result = func(number) print("buggy_incr_by returned ", result) return wrapper@snoopdef buggy_incr_by(number): import random breakpoint() return random.randint(0,10) + numberbuggy_incr_by(3)

我們在第11行放置了一個斷點,運行之后,我們查看堆棧信息如下:

-> command.run()-> self.more = self.interpreter.runsource(text, '', symbol) /home/.../code.py(74)runsource()-> self.runcode(code) /home/.../code.py(90)runcode()-> exec(code, self.locals) (14)() (4)wrapper()

斷點設置在bugg_incr_by中,但顯示的最底層的函數名卻為wrapper,這會使得調試變困難,因此我們需要更正這一信息。

函數作為一種對象,它有以下元屬性:

# __module__, __name__, __qualname__, __doc__, __annotations__for name in ['__module__', '__name__', '__qualname__', '__doc__', '__annotations__']: print(getattr(buggy_incr_by, name))# --output--__main__wrappersnoop..wrapperNone{}

我們需要用功能函數的這些元屬性來改寫替換函數的相關屬性:

setattr(buggy_incr_by, '__name__', 'gime new name')for name in ['__module__', '__name__', '__qualname__', '__doc__', '__annotations__']: print(getattr(buggy_incr_by, name))#--output--__main__gime new namesnoop..wrapperNone{}

通過使用setattr,我們可以很容易替換掉這些信息。我們看到buggy_incr_by現在有了新的名字,即'gime new name'

不過,我們沒有必要親自去做這些瑣事。我們可以在代碼段block 2的第三行,即在wrapper之前,調用functools.wraps來為我們解決這個問題,這里functools.wrapper是另一個裝飾器:

import functoolsdef snoop(func): @functools.wraps(func) # wraps需要接收func參數 def wrapper(number): print("passed in param is ", number) result = func(number) print("buggy_incr_by returned ", result) return wrapper@snoopdef buggy_incr_by(number): import random breakpoint() return random.randint(0,10) + numberbuggy_incr_by.__name__

注意第3行的注釋。很顯然functools.wraps需要這個參數,因為它要從func中獲取`__name__`,` __qualname__`, `__doc__`等信息,以便去更新下面的wraper。實際上,functools.wraps是接收了兩個函數對象作為參數。

總結

以上是生活随笔為你收集整理的python装饰器_python装饰器完全指南之一的全部內容,希望文章能夠幫你解決所遇到的問題。

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