<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文徹底搞懂Python裝飾器

          共 16723字,需瀏覽 34分鐘

           ·

          2021-08-10 02:44

          什么是裝飾器?

          Python裝飾器(fuctional decorators)就是用于拓展原來函數(shù)功能的一種函數(shù),目的是在不改變原函數(shù)名(或類名)的情況下,給函數(shù)增加新的功能。 

          這個(gè)函數(shù)的特殊之處在于它的返回值也是一個(gè)函數(shù),這個(gè)函數(shù)是內(nèi)嵌“原" 函數(shù)的函數(shù)。

          一般而言,我們要想拓展原來函數(shù)代碼,最直接的辦法就是侵入代碼里面修改,例如:

          import time

          def f():
              print("hello")
              time.sleep(1)
              print("world"

          這是我們最原始的的一個(gè)函數(shù),然后我們試圖記錄下這個(gè)函數(shù)執(zhí)行的總時(shí)間,那最簡單的做法就是改動(dòng)原來的代碼:

          import time
          def f():
              start_time = time.time()
              print("hello")
              time.sleep(1)
              print("world")
              end_time = time.time()
              execution_time = (end_time - start_time)*1000
              print("time is %d ms" %execution_time)

          但是實(shí)際工作中,有些時(shí)候核心代碼并不可以直接去改,所以在不改動(dòng)原代碼的情況下,我們可以再定義一個(gè)函數(shù)。(但是生效需要再次執(zhí)行函數(shù))

          import time
          def deco(func):
              start_time = time.time()
              f()
              end_time = time.time()
              execution_time = (end_time - start_time)*1000
              print("time is %d ms" %execution_time)

          def f():
              print("hello")
              time.sleep(1)
              print("world")

          if __name__ == '__main__':
              deco(f)
              print("f.__name__ is",f.__name__)
              print()


          輸出如下:

          helloworldtime is 1000 msf.__name__ is f


          這里我們定義了一個(gè)函數(shù)deco,它的參數(shù)是一個(gè)函數(shù),然后給這個(gè)函數(shù)嵌入了計(jì)時(shí)功能。但是想要拓展這一千萬個(gè)函數(shù)功能,

          就是要執(zhí)行一千萬次deco()函數(shù),所以這樣并不理想!接下來,我們可以試著用裝飾器來實(shí)現(xiàn),先看看裝飾器最原始的面貌。


          import time
          def deco(f):
              def wrapper():
                  start_time = time.time()
                  f()
                  end_time = time.time()
                  execution_time = (end_time - start_time) * 1000
                  print("time is %d ms" % execution_time)

              return wrapper

          @deco
          def f():
              print("hello")
              time.sleep(1)
              print("world")

          if __name__ == '__main__':
              f()

           

          輸出如下:

          helloworldtime is 1000 ms


          這里的deco函數(shù)就是最原始的裝飾器,它的參數(shù)是一個(gè)函數(shù),然后返回值也是一個(gè)函數(shù)。

          其中作為參數(shù)的這個(gè)函數(shù)f()就在返回函數(shù)wrapper()的內(nèi)部執(zhí)行。然后在函數(shù)f()前面加上@deco,

          f()函數(shù)就相當(dāng)于被注入了計(jì)時(shí)功能,現(xiàn)在只要調(diào)用f(),它就已經(jīng)變身為“新的功能更多”的函數(shù)了,(不需要重復(fù)執(zhí)行原函數(shù))。 


          擴(kuò)展1:帶有固定參數(shù)的裝飾器

          import time
          def deco(f):
              def wrapper(a,b):
                  start_time = time.time()
                  f(a,b)
                  end_time = time.time()
                  execution_time = (end_time - start_time)*1000
                  print("time is %d ms" % execution_time)
              return wrapper

          @deco
          def f(a,b):
              print("be on")
              time.sleep(1)
              print("result is %d" %(a+b))

          if __name__ == '__main__':
              f(3,4)


          輸出如下:

          be onresult is 7time is 1000 ms


          擴(kuò)展2:無固定參數(shù)的裝飾器


          import time
          def deco(f):
              def wrapper(*args, **kwargs):
                  start_time = time.time()
                  f(*args, **kwargs)
                  end_time = time.time()
                  execution_time = (end_time - start_time)*1000
                  print("time is %d ms" %execution_time)
              return wrapper

          @deco
          def f(a,b):
              print("be on")
              time.sleep(1)
              print("result is %d" %(a+b))

          @deco
          def f2(a,b,c):
              print("be on")
              time.sleep(1)
              print("result is %d" %(a+b+c))

          if __name__ == '__main__':
              f2(3,4,5)
              f(3,4)


          輸出如下:

          be onresult is 12time is 1000 msbe onresult is 7time is 1000 ms

          擴(kuò)展3:使用多個(gè)裝飾器,裝飾一個(gè)函數(shù)

           

          import  time
          def deco01(f):
              def wrapper(*args, **kwargs):
                  print("this is deco01")
                  start_time = time.time()
                  f(*args, **kwargs)
                  end_time = time.time()
                  execution_time = (end_time - start_time)*1000
                  print("time is %d ms" % execution_time)
                  print("deco01 end here")
              return wrapper

          def deco02(f):
              def wrapper(*args, **kwargs):
                  print("this is deco02")
                  f(*args, **kwargs)
                  print("deco02 end here")
              return wrapper

          @deco01
          @deco02
          def f(a,b):
              print("be on")
              time.sleep(1)
              print("result is %d" %(a+b))

          if __name__ == '__main__':
              f(3,4)


          輸出如下:


          this is deco01this is deco02be onresult is 7deco02 end heretime is 1009 msdeco01 end here


          裝飾器調(diào)用順序

          裝飾器是可以疊加使用的,那么使用裝飾器以后代碼是啥順序呢?

          對于Python中的”@”語法糖,裝飾器的調(diào)用順序與使用 @ 語法糖聲明的順序相反。

          在這個(gè)例子中,f(3, 4) 等價(jià)于 deco01(deco02(f(3, 4)))。


          Python內(nèi)置裝飾器

          在Python中有三個(gè)內(nèi)置的裝飾器,都是跟class相關(guān)的:staticmethod、classmethod 和property。

          • staticmethod 是類靜態(tài)方法,其跟成員方法的區(qū)別是沒有 self 參數(shù),并且可以在類不進(jìn)行實(shí)例化的情況下調(diào)用

          • classmethod 與成員方法的區(qū)別在于所接收的第一個(gè)參數(shù)不是 self (類實(shí)例的指針),而是cls(當(dāng)前類的具體類型)

          • property 是屬性的意思,表示可以通過通過類實(shí)例直接訪問的信息

          對于staticmethod和classmethod這里就不介紹了,通過一個(gè)例子看看property。

          class Foo(object):

              def __init__(self, var):
                  super(Foo, self).__init__()
                  self.__var = var

              @property
              def var(self):
                  return self.__var

              @var.setter
              def var(self, var):
                  self.__var = var


          if __name__ == "__main__":
              foo = Foo("var1")
              print(foo.var)
              foo.var = "var2"
              print(foo.var)


          注意,對于Python新式類(在 py3 里面的繼承 object 的類(默認(rèn)),以及它的子類都是新式類),如果將上面的 “@var.setter” 裝飾器所裝飾的成員函數(shù)去掉,則Foo.var 屬性為只讀屬性,使用 “foo.var = ‘var 2′” 進(jìn)行賦值時(shí)會拋出異常。但是,對于Python classic class,所聲明的屬性不是 read-only的,所以即使去掉”@var.setter”裝飾器也不會報(bào)錯(cuò)。


          參數(shù)化裝飾器

          在實(shí)際代碼中可能需要使用參數(shù)化的裝飾器。如果用函數(shù)作為裝飾器的話,那么解決方法很簡單:再增加一層包裝。例如:


          def repeat(number=3):
              """多次重復(fù)執(zhí)行原始函數(shù),返回最后一次調(diào)用的值作為結(jié)果"""
              def actual_decorator(function):
                  def wrapper(*args, **kwargs):
                      result = None
                      for i in range(number):
                          result = function(*args, **kwargs)
                      return result
                  return wrapper
              return actual_decorator

          @repeat()  # 即使有默認(rèn)參數(shù),這里的括號也不能省略
          def f():
              print("喵")

          @repeat(2)
          def g(a,b):
              print("g函數(shù)執(zhí)行中")
              return a + b

          if __name__ == "__main__":
              f()
              print()
              print(g(1, 2))



          • 保存原始函數(shù)的文檔字符串和函數(shù)名

            看下面的例子:


          def dumy_dec(function):
              def wrapped(*args, **kwargs):
                  """包裝函數(shù)內(nèi)部文檔"""
                  return function(*args, **kwargs)
              return wrapped

          @dumy_dec
          def function():
              """原始的文檔字符串"""

          if __name__ == "__main__":
              print(function.__name__)
              print(function.__doc__)


          使用裝飾器后,我們?nèi)绻氩榭丛己瘮?shù)的函數(shù)名或原始函數(shù)的文檔字符串,返回的卻是:


          wrapped包裝函數(shù)內(nèi)部文檔


          解決這個(gè)問題的正確辦法,是使用functools模塊內(nèi)置的wraps()裝飾器。


          from functools import wraps

          def preserving_dec(function):
              @wraps(function)  # 注意看這里!
              def wrapped(*args, **kwargs):
                  """包裝函數(shù)內(nèi)部文檔"""
                  return function(*args, **kwargs)
              return wrapped

          @preserving_dec
          def function():
              """原始的文檔字符串"""
          if __name__ == "__main__":
              print(function.__name__)
              print(function.__doc__)


          結(jié)果如下:


          function原始的文檔字符串


          用類來實(shí)現(xiàn)裝飾器


          裝飾器函數(shù)其實(shí)是這樣一個(gè)接口約束,它必須接受一個(gè)callable對象作為參數(shù),然后返回一個(gè)callable對象。在Python中一般callable對象都是函數(shù),但也有例外。只要某個(gè)對象重載了__call__()方法,那么這個(gè)對象就是callable的。由此使用用戶自定義的類也可以實(shí)現(xiàn)裝飾器:


          class Decorator:
              def __init__(self, function):
                  self.function = function

              def __call__(self, *args, **kwargs):
                  print("Do something here before call The original function")
                  print(self.function.__doc__)
                  result = self.function(*args, **kwargs)
                  print("Do something here after call The original function")
                  return result

          @Decorator
          def add(a, b):
              """原始函數(shù)的文檔字符串"""
              print("The original function is running")
              return a + b

          if __name__ == "__main__":
              add(1, 2)


          結(jié)果如下:

          Do something here before call The original function原始函數(shù)的文檔字符串The original function is runningDo something here after call The original function


          • 不能裝飾靜態(tài)方法和類方法


          from datetime import datetime

          def logging(func):
              def wrapper(*args, **kwargs):
                  """print log before a function."""
                  print("[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__))
                  return func(*args, **kwargs)
              return wrapper

          class Car(object):
              def __init__(self, model):
                  self.model = model

              @logging  # 裝飾實(shí)例方法,OK
              def run(self):
                  print(f"{self.model} is running!")

              @logging  # 裝飾靜態(tài)方法,F(xiàn)ailed
              @staticmethod
              def check_model_for(obj):
                  if isinstance(obj, Car):
                      print(f"The model of your car is {obj.model}")
                  else:
                      print(f"{obj} is not a car!")

          car = Car("麒麟")
          car.run()
          Car.check_model_for(car)


          會報(bào)錯(cuò):


          Traceback (most recent call last):  File "F:/AutoOps_platform/裝飾器.py", line 253, in <module>    Car.check_model_for(car)  File "F:/AutoOps_platform/裝飾器.py", line 231, in wrapper    print("[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__))AttributeError: 'staticmethod' object has no attribute '__name__'[DEBUG] 2021-08-06 13:11:19.070195: enter run()麒麟 is running!


          @staticmethod 這個(gè)裝飾器,其實(shí)返回的并不是一個(gè)callable對象,而是一個(gè)staticmethod 對象,那么它是不符合裝飾器要求的(比如傳入一個(gè)callable對象),你自然不能在它之上再加別的裝飾器。要解決這個(gè)問題很簡單,只要把你的裝飾器放在@staticmethod 之前就好了:

          @staticmethod
          @logging  # 先裝飾就沒問題
          def check_model_for(obj):
              if isinstance(obj, Car):
                  print(f"The model of your car is {obj.model}")
              else:
                  print(f"{obj} is not a car!")


          [DEBUG] 2021-08-06 13:14:40.404121: enter run()麒麟 is running![DEBUG] 2021-08-06 13:14:40.404121: enter check_model_for()The model of your car is 麒麟


          使用第三方庫優(yōu)化你的裝飾器

          decorator.py 是一個(gè)非常簡單的裝飾器加強(qiáng)包。你可以很直觀的先定義包裝函數(shù)wrapper(),再使用decorate(func, wrapper)方法就可以完成一個(gè)裝飾器。


          from decorator import decorate
          from datetime import datetime

          def wrapper(func, *args, **kwargs):
              """print log before a function."""
              print("[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__))
              return func(*args, **kwargs)

          def logging(func):
              return decorate(func, wrapper)  # 用wrapper裝飾func
          @logging
          def f(a, b):
              return a + b

          f(1,2)


          你也可以使用它自帶的@decorator裝飾器來完成你的裝飾器。


          from decorator import decorator
          from datetime import datetime

          @decorator
          def logging(func, *args, **kwargs):
              print(f"[DEBUG] {datetime.now()}: enter {func.__name__}()")
              return func(*args, **kwargs)

          @logging
          def f(a, b):
              return a + b

          f(1,2)


          [DEBUG] 2021-08-06 13:19:44.920748: enter f()


          decorator.py實(shí)現(xiàn)的裝飾器能完整保留原函數(shù)的name,doc和args。


          也可以使用第三方庫wrapt:

          import wrapt

          @wrapt.decorator  # without argument in decorator
          def logging(wrapped, instance, args, kwargs):  # instance is must
              print(f"[DEBUG]: enter {wrapped.__name__}()")
              return wrapped(*args, **kwargs)

          @logging
          def say(something):
              print(f"is saying {something}")

          say("喵")

          [DEBUG]: enter say()is saying 喵


          使用wrapt你只需要定義一個(gè)裝飾器函數(shù),但是函數(shù)簽名是固定的,必須是(wrapped, instance, args, kwargs),注意第二個(gè)參數(shù)instance是必須的,就算你不用它。當(dāng)裝飾器裝飾在不同位置時(shí)它將得到不同的值,比如裝飾在類實(shí)例方法時(shí)你可以拿到這個(gè)類實(shí)例。根據(jù)instance的值你能夠更加靈活的調(diào)整你的裝飾器。另外,args和kwargs也是固定的,注意前面沒有星號。在裝飾器內(nèi)部調(diào)用原函數(shù)時(shí)才帶星號。


          如果你需要使用wrapt寫一個(gè)帶參數(shù)的裝飾器,可以這樣寫:

          def logging(level):
              @wrapt.decorator
              def wrapper(wrapped, instance, args, kwargs):
                  print("[{}]: enter {}()".format(level, wrapped.__name__))
                  return wrapped(*args, **kwargs)
              return wrapper

          @logging(level="INFO")
          def say(something):
              print(f"is saying {something}")

          say("喵")


          [INFO]: enter say()is saying 喵



          總結(jié):

          本文介紹了Python裝飾器的一些使用,裝飾器的代碼還是比較容易理解的。只要通過一些例子進(jìn)行實(shí)際操作一下,就很容易理解了。



          測試開發(fā)棧

          軟件測試開發(fā)合并必將是趨勢,不懂開發(fā)的測試、不懂測試的開發(fā)都將可能被逐漸替代,因此前瞻的技術(shù)儲備和知識積累是我們以后在職場和行業(yè)脫穎而出的法寶,期望我們的經(jīng)驗(yàn)和技術(shù)分享能讓你每天都成長和進(jìn)步,早日成為測試開發(fā)棧上的技術(shù)大牛~~


          長按二維碼/微信掃描關(guān)注


          歡迎加入QQ群交流和提問:427020613

          互聯(lián)網(wǎng)測試開發(fā)一站式全棧分享平臺


          瀏覽 49
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天操人人操 | 影音先锋男人资源站亚洲AV | 国产传媒精品 | 天天色官网 | 国产成人影视在线观看 |