1. 程式人生 > >【python 裝飾器】深入理解python裝飾器

【python 裝飾器】深入理解python裝飾器

要想徹底搞懂Python中的裝飾器,除了需要有一點Python中的函式基礎,還需要解決如下四個問題。當我們解決了這四個問題後,也就徹底搞懂Python中的裝飾器。
1.什麼是裝飾器,其本質是什麼?
2.裝飾器有什麼作用?
3.裝飾器有什麼使用特點(使用原則)?
4.裝飾器的應用場景
提示:如果你還不知道Python中的函式,請先了解函式後,再來學習。

下面我們依次來回答。

第一部分:什麼是裝飾器,其本質是什麼?

裝飾器是什麼?

  • 從字面意思我們大致可以推測出來,它的作用是用來裝飾的。日常生活中,大家都見過很多裝飾器,舉個最簡單的例子,套在iPhone外面的保護殼。保護殼的存在,並不會改變iPhone內部的功能,它存在的意義,在於增強了iPhone的抗摔效能。

  • Python中的裝飾器也是一樣的道理,它並不會改變被裝飾物件的內部邏輯,而是通過一種無侵入的方式,讓它獲得一些額外的能力,比如日誌記錄、許可權認證、失敗重試等等。

  • Python裝飾器看起來高深莫測,實際上它的實現原理非常簡單。我們知道,在Python中一切皆物件,函式作為一個特殊的物件,可以作為引數傳遞給另外一個函式,裝飾器的工作原理就是基於這一特性。裝飾器的預設語法是使用@來呼叫,這實際上僅僅是一種語法糖。

  • Python中的裝飾器並沒有什麼神祕的,其本質上就是個函式。可以用一個等式理解:裝飾器 = 高階函式 + 巢狀函式。

  • 補充:

    1.函式就是一個物件,函式名是一個指向該物件的變數。
    2.高階函式:引數中有以函式為引數,或者返回值是函式的函式為高階函式
    3.函式巢狀:函式裡面又定義了函式。

第二部分:.裝飾器有什麼作用?

  • 裝飾器是一個程式設計利器,只需一處修改,任何被裝飾的物件就可以獲得額外的功能。
  • 裝飾器,說白了就是用來增強其他函式功能的函式。其形式,通常是在被修飾函式定義時,用@裝飾器名修飾。

來個例子理解一下:

# encoding: utf-8
import datetime


# 定義一個列印日誌的裝飾器
def my_log(func):
    def wrapper(*args, **kwargs):
        print("print_log")
        result = func(*args, **kwargs)
        print("{}函式呼叫時刻:{}".format(func.__name__, datetime.datetime.now()))
        return result
    return wrapper


# 計算平方
@my_log
def cal_square(x):
    result = x*x
    print("{}  * {} = {}".format(x,x, result))
    return result



if __name__ == '__main__':
    x=2
    result=cal_square(x)

    print result

自定義了一個計算一個數平方的函式cal_square,要求呼叫這個函式時列印其呼叫日誌,就可以寫一個日誌裝飾器,假設命名為my_log函式,用來裝飾所要呼叫的函式。

執行結果:

D:\Python27\python.exe F:/PycharmProjects/tom/裝飾器的作用.py
print_log
2  * 2 = 4
cal_square函式呼叫時刻:2018-10-01 21:55:06.638000
4

Process finished with exit code 0
說明:
1.在定義裝飾器時,入參傳入了一個函式,所以裝飾器是一個高階函式,也返回了一個內層函式名。
2.特別提醒: 外層函式的返回結果是巢狀函式wrapper函式名,而不是返回wrapper()。這兩個是不同的概念哦。
3.巢狀函式中的內層函式,函式名可以是任何符合Python命名規則的識別符號。我這裡命名wrapper,你可以自定義為其他合法的函式名。
4.為了保證原函式的呼叫入參不受任何影響,內層函式入參:*args, **kwargs。
5.為了保證原函式的返回值不受任何影響,我們用一個臨時變數result接收,並在內層函式進行返回了result。
6.你還記得前面提到的裝飾器的本質嗎?
其本質上就是個函式。可以用一個等式理解:裝飾器 = 高階函式 + 巢狀函式。

第三部分:裝飾器有什麼使用特點(使用原則)?

  • 1.不會修改被裝飾的函式的原始碼。 (對函式的原始碼沒有任何修改,只是在原來功能的基礎上額外增強函式的功能。)
  • 2.不會改變被裝飾函式的呼叫方式。 (原來怎麼呼叫,被裝飾後依舊怎麼呼叫。)
  • 3.不會改變被裝飾函式的返回結果。

這三點使用特點非常重要,歸結為:原來的函式之前怎麼呼叫、怎麼入參、什麼樣的返回值,使用裝飾器裝飾後這些都不受任何影響。也就是:我們在不對原來函式的原始碼、呼叫形式、返回值等任何修改的情況下,增強了原來函式的功能。

4.裝飾器的應用場景
應用場景非常多,開發中常見的如:

1.插入日誌
2.效能測試
3.處理事務
4.開發python開源框架時,非常常用
5、連線重試

下面再來個裝飾器,在工作中應該用得著,我們知道,程式跑起來後,有一些因素往往是不可控的,比如網路的連通性。斷了怎麼樣,得重試是吧,下面我們定義一個重試機制的裝飾器,可以使用在專案中。


# 定義一個列印方法耗時的裝飾器
def my_time(func):
    def wrapper(*args, **keywords):
        start = time.time()
        print("print_time")
        result = func(*args, **keywords)
        end = time.time()
        t = end - start
        print("{}方法執行耗時:{:.6}秒".format(func.__name__, t))
        return result
     return wrapper

# 定義一個重試機制的裝飾器
def retry(times=10):
    def outer(f):
        def inner(*args, **kwargs):
            for i in xrange(times):
                try:
                    return f(*args, **kwargs)
                except Exception as e:
                    if (i + 1) < times:
                        pass
                    else:
                        raise e

        return inner

    return outer

使用一下


import random
@retry(10)
def non_steady():
    if random.random() <= 0.5:
        # 失敗的概率是 0.5
        raise Exception("died")
    else:
        # 成功的概率是 0.5
        return "survived"


kk=non_steady()

print kk
、D:\Python27\python.exe F:/PycharmProjects/tom/裝飾器的作用.py
survived

Process finished with exit code
# 計算平方
@my_time
@my_log
def cal_square(x):
    time.sleep(3)
    result = x*x
    print("{}  * {} = {}".format(x,x, result))
    return result


cal_square(5)
D:\Python27\python.exe F:/PycharmProjects/tom/裝飾器的作用.py
print_time
print_log
5  * 5 = 25
cal_square函式呼叫時刻:2018-10-01 22:19:01.435000
wrapper方法執行耗時:3.0秒

Process finished with exit code 0

最後總之使用Python裝飾器,可以讓你的程式碼更易維護,可讀性也有一定提升。