Python中級知識之 裝飾器介紹

      網友投稿 882 2025-04-03

      七、函數裝飾器

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

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

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

      7.1 裝飾器基本使用

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

      計算函數運行時間:

      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"函數{fun.__name__}運行時間是:{e_time-s_time}")

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

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

      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"函數{fun.__name__}運行時間是:{e_time-s_time}")

      if __name__ == "__main__":

      go(fun1)

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

      import time

      def go(func):

      # 這里的 wrapper 函數名可以為任意名稱

      def wrapper():

      s_time = time.perf_counter()

      func()

      e_time = time.perf_counter()

      print(f"函數{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 函數部分,它的參數 func 是一個函數,返回值是一個內部函數,執行代碼之后相當于給原函數注入了計算時間的代碼。在代碼調用部分,你沒有做任何修改,函數 func 就具備了更多的功能(計算運行時間的功能)。

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

      7.2 對帶參數的函數進行裝飾

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

      import time

      def go(func):

      def wrapper(x, y):

      s_time = time.perf_counter()

      func(x, y)

      e_time = time.perf_counter()

      print(f"函數{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)

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

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

      def log(text):

      def decorator(func):

      def wrapper(x):

      print('%s %s():' % (text, func.__name__))

      func(x)

      return wrapper

      return decorator

      @log('執行')

      def my_fun(x):

      print(f"我是 my_fun 函數,我的參數 {x}")

      my_fun(123)

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

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

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

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

      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"函數{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)

      代碼運行之后,輸出結果為:

      我是第二個裝飾器

      函數wrapper運行時間是:0.0034401339999999975

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

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

      import time

      def d1(func):

      def wrapper1():

      print("裝飾器1開始裝飾")

      func()

      print("裝飾器1結束裝飾")

      return wrapper1

      def d2(func):

      def wrapper2():

      print("裝飾器2開始裝飾")

      func()

      print("裝飾器2結束裝飾")

      return wrapper2

      @d1

      @d2

      def func():

      print("被裝飾的函數")

      if __name__ == '__main__':

      func()

      上述代碼運行的結果為:

      裝飾器1開始裝飾

      裝飾器2開始裝飾

      被裝飾的函數

      裝飾器2結束裝飾

      裝飾器1結束裝飾

      可以看到非常對稱的輸出,同時證明被裝飾的函數在最內層,轉換成函數調用的代碼如下:

      d1(d2(func))

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

      在對函數進行裝飾的時候,外函數與內函數之間的代碼會被運行。

      測試效果如下:

      import time

      def d1(func):

      print("我是 d1 內外函數之間的代碼")

      def wrapper1():

      print("裝飾器1開始裝飾")

      func()

      print("裝飾器1結束裝飾")

      return wrapper1

      def d2(func):

      print("我是 d2 內外函數之間的代碼")

      def wrapper2():

      print("裝飾器2開始裝飾")

      func()

      print("裝飾器2結束裝飾")

      return wrapper2

      @d1

      @d2

      def func():

      print("被裝飾的函數")

      運行之后,你就能發現輸出結果如下:

      我是 d2 內外函數之間的代碼

      我是 d1 內外函數之間的代碼

      d2 函數早于 d1 函數運行。

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

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

      裝飾函數執行它自己內部的代碼后,會將它的返回值賦值給被裝飾的函數。

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

      # 第一步

      def wrapper2():

      print("裝飾器2開始裝飾")

      print("被裝飾的函數")

      print("裝飾器2結束裝飾")

      # 第二步

      print("裝飾器1開始裝飾")

      wrapper2()

      print("裝飾器1結束裝飾")

      # 第三步

      def wrapper1():

      print("裝飾器1開始裝飾")

      print("裝飾器2開始裝飾")

      print("被裝飾的函數")

      print("裝飾器2結束裝飾")

      print("裝飾器1結束裝飾")

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

      那現在再回到本小節一開始的案例,為何輸出數據丟失掉了。

      import time

      def go(func):

      def wrapper(x, y):

      s_time = time.perf_counter()

      func(x, y)

      e_time = time.perf_counter()

      print(f"函數{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)

      在執行裝飾器代碼裝飾之后,調用 func(33,55) 已經切換為 go(gogo(func)),運行 gogo(func) 代碼轉換為下述內容:

      def wrapper(x, y):

      print("我是第二個裝飾器")

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

      s_time = time.perf_counter()

      print("我是第二個裝飾器")

      e_time = time.perf_counter()

      print(f"函數{func.__name__}運行時間是:{e_time-s_time}")

      此時,你會發現參數在運行過程被丟掉了。

      7.4 functools.wraps大連人流醫院 http://mobile.84211111.cn/

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

      # 裝飾器

      def logged(func):

      def logging(*args, **kwargs):

      print(func.__name__)

      print(func.__doc__)

      func(*args, **kwargs)

      return logging

      # 函數

      @logged

      def f(x):

      """函數文檔,說明"""

      return x * x

      print(f.__name__) # 輸出 logging

      print(f.__doc__) # 輸出 None

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

      from functools import wraps

      # 裝飾器

      def logged(func):

      @wraps(func)

      def logging(*args, **kwargs):

      print(func.__name__)

      print(func.__doc__)

      func(*args, **kwargs)

      return logging

      # 函數

      @logged

      def f(x):

      """函數文檔,說明"""

      return x * x

      print(f.__name__) # 輸出 f

      print(f.__doc__) # 輸出 函數文檔,說明

      7.5 基于類的裝飾器

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

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

      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}'

      Python中級知識之 裝飾器介紹

      s = text('class')

      print(s)

      類 H1 有兩個方法:

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

      __call__:讓類對象可以調用,類似函數調用,觸發點是被裝飾的函數調用時觸發。

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

      在這里類裝飾器的細節就不在展開了,等到后面滾雪球相關項目實操環節再說。

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

      7.6 內置裝飾器

      常見的內置裝飾器有 @property、@staticmethod、@classmethod。該部分內容在細化面向對象部分進行說明,本文只做簡單的備注。

      7.6.1 @property

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

      7.6.2 @staticmethod

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

      7.6.3 @classmethod

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

      Python

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

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

      上一篇:TechED 2015技術大會 迎來Office 2015桌面產品
      下一篇:在表格中怎么找具體內容(怎么從表格中查找內容)
      相關文章
      亚洲AV本道一区二区三区四区| 亚洲第一区精品观看| 亚洲精品无码久久久久YW| 亚洲另类激情综合偷自拍| 精品国产香蕉伊思人在线在线亚洲一区二区| 亚洲s码欧洲m码吹潮| 亚洲老熟女五十路老熟女bbw | 亚洲风情亚Aⅴ在线发布| 亚洲一区在线观看视频| 亚洲人成综合在线播放| 亚洲国产成人资源在线软件| 亚洲国产精品一区二区久| 亚洲导航深夜福利| 亚洲AV成人无码天堂| 亚洲最大无码中文字幕| 亚洲国产精品ⅴa在线观看| 亚洲AV成人一区二区三区观看| 在线亚洲v日韩v| 亚洲综合区小说区激情区| 亚洲伊人久久精品影院| 久久精品夜色国产亚洲av| 666精品国产精品亚洲| 亚洲国产视频一区| 国产亚洲精品VA片在线播放| 亚洲精品成a人在线观看夫| 亚洲av无码偷拍在线观看| 亚洲 综合 国产 欧洲 丝袜| 中文字幕亚洲激情| 亚洲av永久无码精品国产精品| 亚洲综合精品一二三区在线| 亚洲免费电影网站| 亚洲精品国产av成拍色拍| 亚洲日本中文字幕天堂网| 亚洲成av人片在线观看无码不卡| 亚洲专区先锋影音| 亚洲成A人片在线播放器| 国产亚洲视频在线| 亚洲另类激情综合偷自拍图| 亚洲国产精品无码专区| 亚洲美女在线观看播放| 国产精品亚洲综合五月天|