python 閉包 裝飾器

轉自https://www.cnblogs.com/lianyingteng/p/7743876.html

以前你有沒有這樣一段經歷:很久之前你寫過一個函數,現在你突然有了個想法就是你想看看,以前那個函數在你數據集上的運行時間是多少,這時候你可以修改之前代碼爲它加上計時的功能,但是這樣的話是不是還要大體讀讀你之前的這個的代碼,稍微搞清楚一點它的邏輯,纔敢給它添加新的東西。這樣是不是很繁瑣,要是你之前寫的代碼足夠亂足夠長,再去讀它是不是很抓狂...。實際工作中,我們常常會遇到這樣的場景,可能你的需求還不只是這麼簡單。那麼有沒有一種可以不對源碼做任何修改,並且可以很好的實現你所有需求的手段呢?答案當然是有,這就是今天我們要介紹的python裝飾器。有了裝飾器,你除了不用擔心前面提到的問題,並且還可以很好的處理接下來要做的事:那就是現在你又有了一個新的需求,比如爲另一個函數添加計時功能,這時就非常簡單了,把要裝飾的函數丟給裝飾器就好了,它會自動給你添加完功能並返回給你。是不是很神奇?下面我們將一層層剝開它的神祕面紗。

1. 閉包函數

  在看裝飾器之前,我們先來搞清楚什麼是閉包函數。python是一種面向對象的編程語言,在python中一切皆對象,這樣就使得變量所擁有的屬性,函數也同樣擁有。這樣我們就可以理解在函數內創建一個函數的行爲是完全合法的。這種函數被叫做內嵌函數,這種函數只可以在外部函數的作用域內被正常調用,在外部函數的作用域之外調用會報錯,例如:

       

而如果內部函數裏引用了外部函數裏定義的對象(甚至是外層之外,但不是全局變量),那麼此時內部函數就被稱爲閉包函數。閉包函數所引用的外部定義的變量被叫做自由變量。閉包從語法上看非常簡單,但是卻有強大的作用。閉包可以將其自己的代碼和作用域以及外部函數的作用結合在一起。下面給出一個簡單的閉包的例子:

複製代碼

def count():
    a = 1
    b = 1
    def sum():
        c = 1
        return a + c  # a - 自由變量
    return sum

複製代碼

 

 總結:什麼函數可以被稱爲閉包函數呢?主要是滿足兩點:函數內部定義的函數;引用了外部變量但非全局變量。

2. python裝飾器

  有了閉包函數的概念,我們再去理解裝飾器會相對容易一些。python裝飾器本質上就是一個函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外的功能,裝飾器的返回值也是一個函數對象(函數的指針)。裝飾器函數的外部函數傳入我要裝飾的函數名字,返回經過修飾後函數的名字;內層函數(閉包)負責修飾被修飾函數。從上面這段描述中我們需要記住裝飾器的幾點屬性,以便後面能更好的理解:

    實質: 是一個函數

    參數:是你要裝飾的函數名(並非函數調用

    返回:是裝飾完的函數名(也非函數調用

    作用:爲已經存在的對象添加額外的功能

    特點:不需要對對象做任何的代碼上的變動

python裝飾器有很多經典的應用場景,比如:插入日誌、性能測試、事務處理、權限校驗等。裝飾器是解決這類問題的絕佳設計。並且從引入中的列子中我們也可以歸納出:裝飾器最大的作用就是對於我們已經寫好的程序,我們可以抽離出一些雷同的代碼組建多個特定功能的裝飾器,這樣我們就可以針對不同的需求去使用特定的裝飾器,這時因爲源碼去除了大量泛化的內容而使得源碼具有更加清晰的邏輯

2.1  函數裝飾器

函數的函數裝飾器

我們還是以爲函數添加計時功能爲例,講述函數裝飾器。

複製代碼

import time

def decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func()
        end_time = time.time()
        print(end_time - start_time)

    return wrapper

@decorator 
def func():
    time.sleep(0.8)

func() # 函數調用
# 輸出:0.800644397735595

複製代碼

 

在上面代碼中 func是我要裝飾器的函數,我想用裝飾器顯示func函數運行的時間。@decorator這個語法相當於 執行 func = decorator(func),爲func函數裝飾並返回。在來看一下我們的裝飾器函數 - decorator,該函數的傳入參數是func (被裝飾函數),返回參數是內層函數。這裏的內層函數-wrapper,其實就相當於閉包函數,它起到裝飾給定函數的作用,wrapper參數爲*args, **kwargs。*args表示的參數以列表的形式傳入;**kwargs表示的參數以字典的形式傳入:

        

從圖中我們可以看到:凡是以key=value形式的參數均存在kwargs中,剩下的所有參數都以列表的形式存於args中。這裏要注意的是:爲了不破壞原函數的邏輯,我們要保證內層函數wrapper和被裝飾函數func的傳入參數和返回值類型必須保持一致。

類方法的函數裝飾器

  類方法的函數裝飾器和函數的函數裝飾器類似。

複製代碼

import time

def decorator(func):
    def wrapper(me_instance):
        start_time = time.time()
        func(me_instance)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper

class Method(object):

    @decorator 
    def func(self):
        time.sleep(0.8)

p1 = Method()
p1.func() # 函數調用

複製代碼

對於類方法來說,都會有一個默認的參數self,它實際表示的是類的一個實例,所以在裝飾器的內部函數wrapper也要傳入一個參數 - me_instance就表示將類的實例p1傳給wrapper,其他的用法都和函數裝飾器相同。

2.2 類裝飾器

  前面我們提到的都是讓 函數作爲裝飾器去裝飾其他的函數或者方法,那麼可不可以讓 一個類發揮裝飾器的作用呢?答案肯定是可以的,一切皆對象嚒,函數和類本質沒有什麼不一樣。類的裝飾器是什麼樣子的呢?

複製代碼

class Decorator(object):
    def __init__(self, f):
        self.f = f
    def __call__(self):
        print("decorator start")
        self.f()
        print("decorator end")

@Decorator
def func():
    print("func")

func()

複製代碼

這裏有注意的是:__call__()是一個特殊方法,它可將一個類實例變成一個可調用對象:

 

p = Decorator(func) # p是類Decorator的一個實例
p() # 實現了__call__()方法後,p可以被調用

要使用類裝飾器必須實現類中的__call__()方法,就相當於將實例變成了一個方法。

2.3  裝飾器鏈

  一個python函數也可以被多個裝飾器修飾,要是有多個裝飾器時,這些裝飾器的執行順序是怎麼樣的呢?

        

可見,多個裝飾器的執行順序:是從近到遠依次執行。

2.4  python裝飾器庫 - functools

複製代碼

def decorator(func):
    def inner_function():
        pass
    return inner_function

@decorator
def func():
    pass

print(func.__name__)

# 輸出: inner_function

複製代碼

 上述代碼最後執行的結果不是 func,而是 inner_function!這表示被裝飾函數自身的信息丟失了!怎麼才能避免這種問題的發生呢?

可以藉助functools.wraps()函數:

複製代碼

from functools import wraps
def decorator(func):
    @wraps(func) 
    def inner_function():
        pass
    return inner_function

@decorator
def func():
    pass

print(func.__name__)

#輸出: func
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章