Python 中級知識之裝飾器,滾雪球?qū)W Python

      網(wǎng)友投稿 703 2022-05-30

      橡皮擦,一個逗趣的互聯(lián)網(wǎng)高級網(wǎng)蟲,新的系列,讓我們一起 Be More Pythonic。

      七、函數(shù)裝飾器

      裝飾器(Decorators)在 Python 中,主要作用是修改函數(shù)的功能,而且修改前提是不變動原函數(shù)代碼,裝飾器會返回一個函數(shù)對象,所以有的地方會把裝飾器叫做 “函數(shù)的函數(shù)”。

      還存在一種設(shè)計模式叫做 “裝飾器模式”,這個后續(xù)的課程會有所涉及。

      裝飾器調(diào)用的時候,使用 @,它是 Python 提供的一種編程語法糖,使用了之后會讓你的代碼看起來更加 Pythonic。

      7.1 裝飾器基本使用

      在學習裝飾器的時候,最常見的一個案例,就是統(tǒng)計某個函數(shù)的運行時間,接下來就為你分享一下。

      計算函數(shù)運行時間:

      import time def fun(): i = 0 while i < 1000: i += 1 def fun1(): i = 0 while i < 10000: i += 1 s_time = time.perf_counter() fun() e_time = time.perf_counter() print(f"函數(shù){fun.__name__}運行時間是:{e_time-s_time}")

      如果你希望給每個函授都加上調(diào)用時間,那工作量是巨大的,你需要重復的修改函數(shù)內(nèi)部代碼,或者修改函數(shù)調(diào)用位置的代碼。在這種需求下,裝飾器語法出現(xiàn)了。

      先看一下第一種修改方法,這種方法沒有增加裝飾器,但是編寫了一個通用的函數(shù),利用 Python 中函數(shù)可以作為參數(shù)這一特性,完成了代碼的可復用性。

      import time def fun(): i = 0 while i < 1000: i += 1 def fun1(): i = 0 while i < 10000: i += 1 def go(fun): s_time = time.perf_counter() fun() e_time = time.perf_counter() print(f"函數(shù){fun.__name__}運行時間是:{e_time-s_time}") if __name__ == "__main__": go(fun1)

      接下來這種技巧擴展到 Python 中的裝飾器語法,具體修改如下:

      import time def go(func): # 這里的 wrapper 函數(shù)名可以為任意名稱 def wrapper(): s_time = time.perf_counter() func() e_time = time.perf_counter() print(f"函數(shù){func.__name__}運行時間是:{e_time-s_time}") return wrapper @go def func(): i = 0 while i < 1000: i += 1 @go def func1(): i = 0 while i < 10000: i += 1 if __name__ == '__main__': func()

      在上述代碼中,注意看 go 函數(shù)部分,它的參數(shù) func 是一個函數(shù),返回值是一個內(nèi)部函數(shù),執(zhí)行代碼之后相當于給原函數(shù)注入了計算時間的代碼。在代碼調(diào)用部分,你沒有做任何修改,函數(shù) func 就具備了更多的功能(計算運行時間的功能)。

      裝飾器函數(shù)成功拓展了原函數(shù)的功能,又不需要修改原函數(shù)代碼,這個案例學會之后,你就已經(jīng)初步了解了裝飾器。

      7.2 對帶參數(shù)的函數(shù)進行裝飾

      直接看代碼,了解如何對帶參數(shù)的函數(shù)進行裝飾:

      import time def go(func): def wrapper(x, y): s_time = time.perf_counter() func(x, y) e_time = time.perf_counter() print(f"函數(shù){func.__name__}運行時間是:{e_time-s_time}") return wrapper @go def func(x, y): i = 0 while i < 1000: i += 1 print(f"x={x},y={y}") if __name__ == '__main__': func(33, 55)

      如果你看著暈乎了,我給你標記一下參數(shù)的重點傳遞過程。

      還有一種情況是裝飾器本身帶有參數(shù),例如下述代碼:

      def log(text): def decorator(func): def wrapper(x): print('%s %s():' % (text, func.__name__)) func(x) return wrapper return decorator @log('執(zhí)行') def my_fun(x): print(f"我是 my_fun 函數(shù),我的參數(shù) {x}") my_fun(123)

      上述代碼在編寫裝飾器函數(shù)的時候,在裝飾器函數(shù)外層又嵌套了一層函數(shù),最終代碼的運行順序如下所示:

      my_fun = log('執(zhí)行')(my_fun)

      此時如果我們總結(jié)一下,就能得到結(jié)論了:使用帶有參數(shù)的裝飾器,是在裝飾器外面又包裹了一個函數(shù),使用該函數(shù)接收參數(shù),并且返回一個裝飾器函數(shù)。

      還有一點要注意的是裝飾器只能接收一個參數(shù),而且必須是函數(shù)類型。

      7.3 多個裝飾器

      先臨摹一下下述代碼,再進行學習與研究。

      import time def go(func): def wrapper(x, y): s_time = time.perf_counter() func(x, y) e_time = time.perf_counter() print(f"函數(shù){func.__name__}運行時間是:{e_time-s_time}") return wrapper def gogo(func): def wrapper(x, y): print("我是第二個裝飾器") return wrapper @go @gogo def func(x, y): i = 0 while i < 1000: i += 1 print(f"x={x},y={y}") if __name__ == '__main__': func(33, 55)

      代碼運行之后,輸出結(jié)果為:

      我是第二個裝飾器 函數(shù)wrapper運行時間是:0.0034401339999999975

      雖說多個裝飾器使用起來非常簡單,但是問題也出現(xiàn)了,print(f"x={x},y={y}") 這段代碼運行結(jié)果丟失了,這里就涉及多個裝飾器執(zhí)行順序問題了。

      先解釋一下裝飾器的裝飾順序。

      import time def d1(func): def wrapper1(): print("裝飾器1開始裝飾") func() print("裝飾器1結(jié)束裝飾") return wrapper1 def d2(func): def wrapper2(): print("裝飾器2開始裝飾") func() print("裝飾器2結(jié)束裝飾") return wrapper2 @d1 @d2 def func(): print("被裝飾的函數(shù)") if __name__ == '__main__': func()

      上述代碼運行的結(jié)果為:

      裝飾器1開始裝飾 裝飾器2開始裝飾 被裝飾的函數(shù) 裝飾器2結(jié)束裝飾 裝飾器1結(jié)束裝飾

      可以看到非常對稱的輸出,同時證明被裝飾的函數(shù)在最內(nèi)層,轉(zhuǎn)換成函數(shù)調(diào)用的代碼如下:

      d1(d2(func))

      你在這部分需要注意的是,裝飾器的外函數(shù)和內(nèi)函數(shù)之間的語句,是沒有裝飾到目標函數(shù)上的,而是在裝載裝飾器時的附加操作。

      在對函數(shù)進行裝飾的時候,外函數(shù)與內(nèi)函數(shù)之間的代碼會被運行。

      測試效果如下:

      import time def d1(func): print("我是 d1 內(nèi)外函數(shù)之間的代碼") def wrapper1(): print("裝飾器1開始裝飾") func() print("裝飾器1結(jié)束裝飾") return wrapper1 def d2(func): print("我是 d2 內(nèi)外函數(shù)之間的代碼") def wrapper2(): print("裝飾器2開始裝飾") func() print("裝飾器2結(jié)束裝飾") return wrapper2 @d1 @d2 def func(): print("被裝飾的函數(shù)")

      運行之后,你就能發(fā)現(xiàn)輸出結(jié)果如下:

      我是 d2 內(nèi)外函數(shù)之間的代碼 我是 d1 內(nèi)外函數(shù)之間的代碼

      d2 函數(shù)早于 d1 函數(shù)運行。

      接下來在回顧一下裝飾器的概念:

      被裝飾的函數(shù)的名字會被當作參數(shù)傳遞給裝飾函數(shù)。

      裝飾函數(shù)執(zhí)行它自己內(nèi)部的代碼后,會將它的返回值賦值給被裝飾的函數(shù)。

      這樣看上文中的代碼運行過程是這樣的,d1(d2(func)) 執(zhí)行 d2(func) 之后,原來的 func 這個函數(shù)名會指向 wrapper2 函數(shù),執(zhí)行 d1(wrapper2) 函數(shù)之后,wrapper2 這個函數(shù)名又會指向 wrapper1。因此最后的 func 被調(diào)用的時候,相當于代碼已經(jīng)切換成如下內(nèi)容了。

      # 第一步 def wrapper2(): print("裝飾器2開始裝飾") print("被裝飾的函數(shù)") print("裝飾器2結(jié)束裝飾") # 第二步 print("裝飾器1開始裝飾") wrapper2() print("裝飾器1結(jié)束裝飾") # 第三步 def wrapper1(): print("裝飾器1開始裝飾") print("裝飾器2開始裝飾") print("被裝飾的函數(shù)") print("裝飾器2結(jié)束裝飾") print("裝飾器1結(jié)束裝飾")

      上述第三步運行之后的代碼,恰好與我們的代碼輸出一致。

      那現(xiàn)在再回到本小節(jié)一開始的案例,為何輸出數(shù)據(jù)丟失掉了。

      import time def go(func): def wrapper(x, y): s_time = time.perf_counter() func(x, y) e_time = time.perf_counter() print(f"函數(shù){func.__name__}運行時間是:{e_time-s_time}") return wrapper def gogo(func): def wrapper(x, y): print("我是第二個裝飾器") return wrapper @go @gogo def func(x, y): i = 0 while i < 1000: i += 1 print(f"x={x},y={y}") if __name__ == '__main__': func(33, 55)

      在執(zhí)行裝飾器代碼裝飾之后,調(diào)用 func(33,55) 已經(jīng)切換為 go(gogo(func)),運行 gogo(func) 代碼轉(zhuǎn)換為下述內(nèi)容:

      def wrapper(x, y): print("我是第二個裝飾器")

      在運行 go(wrapper),代碼轉(zhuǎn)換為:

      s_time = time.perf_counter() print("我是第二個裝飾器") e_time = time.perf_counter() print(f"函數(shù){func.__name__}運行時間是:{e_time-s_time}")

      此時,你會發(fā)現(xiàn)參數(shù)在運行過程被丟掉了。

      7.4 functools.wraps

      使用裝飾器可以大幅度提高代碼的復用性,但是缺點就是原函數(shù)的元信息丟失了,比如函數(shù)的 __doc__、__name__:

      # 裝飾器 def logged(func): def logging(*args, **kwargs): print(func.__name__) print(func.__doc__) func(*args, **kwargs) return logging # 函數(shù) @logged def f(x): """函數(shù)文檔,說明""" return x * x print(f.__name__) # 輸出 logging print(f.__doc__) # 輸出 None

      解決辦法非常簡單,導入 from functools import wraps ,修改代碼為下述內(nèi)容:

      from functools import wraps # 裝飾器 def logged(func): @wraps(func) def logging(*args, **kwargs): print(func.__name__) print(func.__doc__) func(*args, **kwargs) return logging # 函數(shù) @logged def f(x): """函數(shù)文檔,說明""" return x * x print(f.__name__) # 輸出 f print(f.__doc__) # 輸出 函數(shù)文檔,說明

      7.5 基于類的裝飾器

      在實際編碼中 一般 “函數(shù)裝飾器” 最為常見,“類裝飾器” 出現(xiàn)的頻率要少很多。

      基于類的裝飾器與基于函數(shù)的基本用法一致,先看一段代碼:

      class H1(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return '

      ' + self.func(*args, **kwargs) + '

      ' @H1 def text(name): return f'text {name}' s = text('class') print(s)

      類 H1 有兩個方法:

      __init__:接收一個函數(shù)作為參數(shù),就是待被裝飾的函數(shù);

      __call__:讓類對象可以調(diào)用,類似函數(shù)調(diào)用,觸發(fā)點是被裝飾的函數(shù)調(diào)用時觸發(fā)。

      最后在附錄一篇寫的不錯的 博客,可以去學習。

      在這里類裝飾器的細節(jié)就不在展開了,等到后面滾雪球相關(guān)項目實操環(huán)節(jié)再說。

      裝飾器為類和類的裝飾器在細節(jié)上是不同的,上文提及的是裝飾器為類,你可以在思考一下如何給類添加裝飾器。

      7.6 內(nèi)置裝飾器

      常見的內(nèi)置裝飾器有 @property、@staticmethod、@classmethod。該部分內(nèi)容在細化面向?qū)ο蟛糠诌M行說明,本文只做簡單的備注。

      7.6.1 @property

      把類內(nèi)方法當成屬性來使用,必須要有返回值,相當于 getter,如果沒有定義 @func.setter 修飾方法,是只讀屬性。

      7.6.2 @staticmethod

      靜態(tài)方法,不需要表示自身對象的 self 和自身類的 cls 參數(shù),就跟使用函數(shù)一樣。

      7.6.3 @classmethod

      類方法,不需要 self 參數(shù),但第一個參數(shù)需要是表示自身類的 cls 參數(shù)。

      7.7 這篇博客的總結(jié)

      關(guān)于 Python 裝飾器,網(wǎng)上的文章實在太太多了,學習起來并不是很難,真正難的是恰到好處的應用在項目中,希望本篇博客能對你理解裝飾器有所幫助。

      其他內(nèi)容也可以查閱 官方手冊。

      Python 中級知識之裝飾器,滾雪球?qū)W Python

      博主 ID:夢想橡皮擦,希望大家

      評論

      、

      。

      Python

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:華為云ModelArts結(jié)合AnimeGANv2,讓照片在云上動漫化 丨【華為云AI賀新年】
      下一篇:【Java】【并發(fā)編程】入門知識
      相關(guān)文章
      亚洲精品亚洲人成在线观看下载| 亚洲啪啪免费视频| 亚洲日韩亚洲另类激情文学| 久久国产亚洲精品无码| 亚洲成AV人片在| 亚洲中文字幕无码不卡电影| 亚洲欧洲精品成人久久曰影片| mm1313亚洲国产精品无码试看| 亚洲youwu永久无码精品| 亚洲久热无码av中文字幕| 亚洲熟妇无码八V在线播放| 久久乐国产综合亚洲精品| 亚洲高清有码中文字| 亚洲AV综合色区无码二区爱AV| 亚洲日本va在线观看| 欧洲 亚洲 国产图片综合| 亚洲精品国产精品国自产网站| 亚洲日韩国产精品乱-久| 亚洲午夜精品一区二区麻豆| 亚洲精品女同中文字幕| 亚洲avav天堂av在线网毛片| 国产亚洲精品成人久久网站| 亚洲?v无码国产在丝袜线观看| 亚洲福利精品电影在线观看| 亚洲精品视频在线观看你懂的| 久久久久亚洲AV成人网| 亚洲熟妇中文字幕五十中出| 亚洲AV无码一区二区乱孑伦AS| 亚洲天堂在线播放| 亚洲国产日韩女人aaaaaa毛片在线| 亚洲av一本岛在线播放| 亚洲精品无码久久久久YW| 国产精品亚洲а∨天堂2021| 亚洲一级片免费看| 亚洲欭美日韩颜射在线二| 亚洲AV成人一区二区三区AV| 亚洲网站视频在线观看| 色婷五月综激情亚洲综合| 亚洲欧美日韩久久精品| 无码国产亚洲日韩国精品视频一区二区三区 | 亚洲AV日韩AV永久无码免下载|