python基礎(8)-裝飾器函數&進階

從小例子進入裝飾器

統計一個函數執行耗時

  • 原始版本

     1 import time
     2 
     3 # time模塊有提供時間相關函數
     4 def do_something():
     5     print("do_something")
     6     time.sleep(0.5)  # 讓程序中止0.5秒模擬其它操做耗時
     7 
     8 start = time.time()
     9 do_something()
    10 print(time.time() - start)
    11 #result:
    12 # do_something
    13 # 0.5000283718109131

    問題:上述代碼能夠完成這個功能,但以後會發現,若是咱們要統計其它函數,就必須在每一個函數先後加入相應代碼python

  • 裝飾器版本1(無參數)

     1 import time
     2 
     3 def execute_time(func):
     4     def inner():
     5         start = time.time()
     6         func()
     7         print(time.time() - start)
     8 
     9     return inner
    10 
    11 # time模塊有提供時間相關函數
    12 def do_something():
    13     print("do_something")
    14     time.sleep(0.5)  # 讓程序中止0.5秒模擬其它操做耗時
    15 
    16 do_something = execute_time(do_something)
    17 do_something()
    18 #result:
    19 # do_something
    20 # 0.5000283718109131

    從上述代碼能夠看到,使用了另外一個函數execute_time()給咱們要統計耗時的函數進行了包裝,這時,這個execute_time()函數就叫作裝飾器函數,而咱們要統計的那個函數也就是do_something()函數就是被裝飾的函數.問題:函數執行的時候其實是調用的execute_time()函數中的inner()函數,這種方法雖然解決了原始版本的問題,可是當咱們要統計的函數擁有返回值的時候,這時候咱們獲取不到返回值.app

  • 裝飾器版本2(有固定參數)

    import time
    
    def execute_time(func):
        def inner(do):
            start = time.time()
            result = func(do)
            print(time.time() - start)
            return result
        return inner
    
    # time模塊有提供時間相關函數
    def do_something(do):
        print("do_something", do)
        time.sleep(0.5)  # 讓程序中止0.5秒模擬其它操做耗時
        return 'do_something over'
    
    do_something = execute_time(do_something)
    print(do_something('say hello'))
    # result:
    # do_something say hello
    # 0.5000283718109131
    # do_something over

    爲了解決裝飾器版本1的問題,我在inner()函數裏面加了個返回值.問題:當被裝飾函數的參數個數與inner()參數個數不一樣時,這個裝飾器就不適用了函數

  • 裝飾器版本3(動態參數)

     1 import time
     2 
     3 def execute_time(func):
     4     def inner(*args, **kwargs):
     5         start = time.time()
     6         result = func(*args, **kwargs)
     7         print(time.time() - start)
     8         return result
     9 
    10     return inner
    11 
    12 # time模塊有提供時間相關函數
    13 def do_something(do1,do2):
    14     print("do_something", do1,do2)
    15     time.sleep(0.5)  # 讓程序中止0.5秒模擬其它操做耗時
    16     return 'do_something over'
    17 
    18 do_something = execute_time(do_something)
    19 print(do_something('say hello1','say hello2'))
    20 # result:
    21 # do_something say hello1 say hello2
    22 # 0.5000283718109131
    23 # do_something over

    在第七天內容中有個知識點是動態參數,恰好能夠解決這個問題spa

  • 終極版本(語法糖@)

     1 import time
     2 
     3 def execute_time(func):
     4     def inner(*args, **kwargs):
     5         start = time.time()
     6         result = func(*args, **kwargs)
     7         print(time.time() - start)
     8         return result
     9 
    10     return inner
    11 
    12 @execute_time
    13 def do_something(do1,do2):
    14     print("do_something", do1,do2)
    15     time.sleep(0.5)  # 讓程序中止0.5秒模擬其它操做耗時
    16     return 'do_something over'
    17 
    18 # do_something = execute_time(do_something)
    19 print(do_something('say hello1','say hello2'))
    20 # result:
    21 # do_something say hello1 say hello2
    22 # 0.5000283718109131
    23 # do_something over

    對於裝飾器,python內部給咱們提供了語法糖支持.在須要被裝飾的函數名上部使用[@裝飾器函數名稱]便可,簡化上述代碼18行code

裝飾器進階

獲取被包裝函數原生屬性

  • 例1:常規函數取函數名

    1 def func():
    2     print('執行中')
    3     print(func.__name__)
    4 
    5 func()
    6 # result:
    7 #   執行中
    8 #   func

    常規函數能夠經過函數的__name__屬性可拿到當前函數名稱orm

  • 例2:被裝飾函數取函數名

     1 def wrapper(func):
     2     def inner():
     3         print('執行前')
     4         result = func()
     5         print('執行後')
     6         return result
     7     return inner;
     8 @wrapper
     9 def func():
    10     print('執行中')
    11     print(func.__name__)
    12 
    13 func()
    14 # result:
    15 #   執行前
    16 #   執行中
    17 #   inner
    18 #   執行後

     問題:經過執行結果會發現,結果中想獲取的函數名是func,而實際結果是inner.緣由是@wrapper進行包裝至關於執行一個操做:func=wrapper(func)=innerblog

  • 解決

    使用functools模塊form

     1 from functools import wraps
     2 
     3 def wrapper(func):
     4     @wraps(func)
     5     def inner():
     6         print('執行前')
     7         result = func()
     8         print('執行後')
     9         return result
    10 
    11     return inner;
    12 
    13 @wrapper
    14 def func():
    15     print('執行中')
    16     print(func.__name__)
    17 
    18 func()
    19 # result:
    20 #   執行前
    21 #   執行中
    22 #   func
    23 #   執行後

    導入functools模塊後經過第4行操做,會發現執行的函數即便被包裝但仍是能獲取到它自己的屬性class

帶參數的裝飾器

  • 例1

     1 def wrapper(func):
     2     def inner():
     3         print('執行前')
     4         result = func()
     5         print('執行後')
     6         return result
     7 
     8     return inner;
     9 
    10 @wrapper
    11 def func_1():
    12     print('執行中')
    13 
    14 @wrapper
    15 def func_2():
    16     print('執行中')
    17 
    18 @wrapper
    19 def func_3():
    20     print('執行中')
    21 ...
    22 @wrapper
    23 def func_n():
    24     print('執行中')

    問題:經過上述代碼會發現,有不少函數都用了同一個裝飾器,若是有一天要取消這些函數上的裝飾,就必須對每個函數進行修改import

  • 解決

    定義一個全局變量flag,並給本來裝飾器外部再加一層函數用來接收參數,inner()函數內部經過外部參數flag判斷被裝飾函數的執行與否,修改flag便可控制裝飾器的執行結果,以下:

     1 from functools import wraps
     2 flag = True
     3 
     4 def wrapper_out(flag):
     5     def wrapper(func):
     6         @wraps(func)
     7         def inner():
     8             if (flag):
     9                 print('{}執行前'.format(func.__name__))
    10                 result = func()
    11                 print('{}執行後'.format(func.__name__))
    12             else:
    13                 result = func()
    14             return result
    15 
    16         return inner
    17 
    18     return wrapper
    19 
    20 @wrapper_out(flag)
    21 def func_1():
    22     print('{}執行中'.format(func_1.__name__))
    23 
    24 @wrapper_out(flag)
    25 def func_2():
    26     print('{}執行中'.format(func_2.__name__))
    27 
    28 @wrapper_out(flag)
    29 def func_3():
    30     print('{}執行中'.format(func_3.__name__))
    31 
    32 ...
    33 
    34 @wrapper_out(flag)
    35 def func_n():
    36     print('{}執行中'.format(func_n.__name__))
    37 
    38 func_1()
    39 func_2()
    40 func_3()
    41 func_n()
    42 
    43 #result:
    44     # func_1執行前
    45     # func_1執行中
    46     # func_1執行後
    47     # func_2執行前
    48     # func_2執行中
    49     # func_2執行後
    50     # func_3執行前
    51     # func_3執行中
    52     # func_3執行後
    53     # func_n執行前
    54     # func_n執行中
    55     # func_n執行後
    from functools import wraps
    flag = False
    
    def wrapper_out(flag):
        def wrapper(func):
            @wraps(func)
            def inner():
                if (flag):
                    print('{}執行前'.format(func.__name__))
                    result = func()
                    print('{}執行後'.format(func.__name__))
                else:
                    result = func()
                return result
    
            return inner
    
        return wrapper
    
    @wrapper_out(flag)
    def func_1():
        print('{}執行中'.format(func_1.__name__))
    
    @wrapper_out(flag)
    def func_2():
        print('{}執行中'.format(func_2.__name__))
    
    @wrapper_out(flag)
    def func_3():
        print('{}執行中'.format(func_3.__name__))
    
    ...
    
    @wrapper_out(flag)
    def func_n():
        print('{}執行中'.format(func_n.__name__))
    
    func_1()
    func_2()
    func_3()
    func_n()
    
    #result:
        # func_1執行中
        # func_2執行中
        # func_3執行中
        # func_n執行中

多個裝飾器裝飾同一個函數

  • 代碼

     1 def wrapper1(func):
     2     def inner():
     3         print('wrapper1 ,before func')
     4         func()
     5         print('wrapper1 ,after func')
     6 
     7     return inner
     8 
     9 def wrapper2(func):
    10     def inner():
    11         print('wrapper2 ,before func')
    12         func()
    13         print('wrapper2 ,after func')
    14 
    15     return inner
    16 
    17 @wrapper1
    18 @wrapper2
    19 def f():
    20     print('in f')
    21 
    22 f()
    23 # result:
    24 # wrapper1 ,before func
    25 # wrapper2 ,before func
    26 # in f
    27 # wrapper2 ,after func
    28 # wrapper1 ,after func
  • 圖解

    從上圖能夠看到,從1-9步是裝飾器的裝載過程,10-18步是執行過程.結論:多個裝飾器裝飾同一個函數時,裝載順序是從下到上,但執行順序倒是從上到下,能夠理解爲建立了一個裝飾器棧(先進後出)

相關文章
相關標籤/搜索