1. 程式人生 > >python裝飾器入門詳解

python裝飾器入門詳解

最近在學習python裝飾器,本篇是根據很多部落格學習整理而來,做個學習筆記!!
1. 函式

在python中,函式通過def關鍵字、函式名和可選的引數列表定義。通過return關鍵字返回值。我們舉例來說明如
何定義和呼叫一個簡單的函式:
方法體是必須的,通過縮排來表示,在方法名的後面加上雙括號()就能夠呼叫函式.

在這裡插入圖片描述

2. 作用域

在python中,函式會建立一個新的作用域。python開發者可能會說函式有自己的名稱空間,差不多一個意思。這意味著
在函式內部碰到一個變數的時候函式會優先在自己的名稱空間裡面去尋找。
內建的函式globals返回一個包含所有python直譯器知道的變數名稱的字典,當呼叫了函式foo把函式內部本地作用域裡面
的內容打印出來。我們能夠看到,函式foo有自己獨立的名稱空間,雖然暫時名稱空間裡面什麼都還沒有。

在這裡插入圖片描述

3. 變數解析規則

當然這並不是說我們在函式裡面就不能訪問外面的全域性變數。在python的作用域規則裡面,建立變數一定會一定會在當前
作用域裡建立一個變數,但是訪問或者修改變數時會先在當前作用域查詢變數,沒有找到匹配變數的話會依次向上在閉合
的作用域裡面進行檢視找。所以如果我們修改函式foo的實現讓它列印全域性的作用域裡的變數也是可以的:

在這裡插入圖片描述

python直譯器會嘗試查詢變數a_string,當然在函式的本地作用域裡面是找不到的,所以接著會去上層的作用域裡面去查詢。
但是另一方面,假如我們在函式內部給全域性變數賦值,結果卻和我們想的不一樣:

在這裡插入圖片描述

我們能夠看到,全域性變數能夠被訪問到(如果是可變資料型別(像list,dict這些)甚至能夠被更改)但是賦值不行。在函式內部
我們實際上新建立了一個區域性變數,隱藏全域性作用域中的同名變數。我們可以通過打印出區域性名稱空間中的內容得出這個結論。
我們也能看到打印出來的變數a_string的值並沒有改變依舊是定義的全域性變數的值。

4. 變數生存週期

值得注意的一個點是,變數不僅是生存在一個個的名稱空間內,他們都有自己的生存週期,請看下面這個例子:

在這裡插入圖片描述

發生的錯誤不僅僅是因為作用域規則導致的(儘管這是丟擲了NameError的錯誤的原因)它還和python以及其它很多程式設計
語言中函式呼叫實現的機制有關。在這個地方這個執行時間點並沒有什麼有效的語法讓我們能夠獲取變數x的值,因為它
這個時候壓根不存在!函式foo的名稱空間隨著函式呼叫開始而開始,結束而銷燬。

在這裡插入圖片描述

5. 函式引數

python允許我們向函式傳遞引數,引數會變成本地變數存在於函式內部。

在這裡插入圖片描述

函式的引數可以是必須的位置引數或者是可選的命名,預設引數。

在這裡插入圖片描述

6. 巢狀函式

Python允許建立巢狀函式。這意味著我們可以在函式裡面定義函式而且現有的作用域和變數生存週期依舊適用。

在這裡插入圖片描述

想一想在#1發生了什麼:python直譯器需找一個叫x的本地變數,查詢失敗之後會繼續在上層的作用域裡面尋找,這個上層
的作用域定義在另外一個函式裡面。對函式outer來說,變數x是一個本地變數,但是如先前提到的一樣,函式inner可以訪問
封閉的作用域(至少可以讀和修改)。在#2處,我們呼叫函式inner,非常重要的一點是,inner也僅僅是一個遵循python變數
解析規則的變數名,python直譯器會優先在outer的作用域裡面對變數名inner查詢匹配的變數.

7.函式的屬性,python一切皆物件,函式在python裡面也是物件
在這裡插入圖片描述
簡單的函式傳參:
在這裡插入圖片描述

函式返回值:
在這裡插入圖片描述

8.閉包:

在這裡插入圖片描述

所有的東西都在python的作用域規則下進行工作:“x是函式outer裡的一個區域性變數。當函式inner在#1處列印x的時候,
python直譯器會在inner內部查詢相應的變數,當然會找不到,所以接著會到封閉作用域裡面查詢,並且會找到匹配。

但是從變數的生存週期來看,該怎麼理解呢?我們的變數x是函式outer的一個本地變數,這意味著只有當函式outer正在執行的
時候才會存在。根據我們已知的python執行模式,我們沒法在函式outer返回之後繼續呼叫函式inner,在函式inner被呼叫的時候,
變數x早已不復存在,可能會發生一個執行時錯誤。

記住,每次函式outer被呼叫的時候,函式inner都會被重新定義。現在變數x的值不會變化,所以每次返回的函式inner會是同樣
的邏輯,假如我們稍微改動一下呢?

在這裡插入圖片描述

從這個例子中你能夠看到閉包 – 被函式記住的封閉作用域 – 能夠被用來建立自定義的函式,本質上來說是一個硬編碼的
引數。事實上我們並不是傳遞引數1或者2給函式inner,我們實際上是建立了能夠列印各種數字的各種自定義版本。

裝飾器

裝飾器其實就是一個閉包,把一個函式當做引數然後返回一個替代版函式。

在這裡插入圖片描述

寫一個有用的裝飾器:

想象我們有一個庫,這個庫能夠提供類似座標的物件,也許它們僅僅是一些x和y的座標對。不過可惜的是這些座標物件不支援
數學運算子,而且我們也不能對原始碼進行修改,因此也就不能直接加入運算子的支援。我們將會做一系列的數學運算,所以
我們想要能夠對兩個座標物件進行合適加減運算的函式,這些方法很容易就能寫出:

在這裡插入圖片描述

如果不巧我們的加減函式同時也需要一些邊界檢查的行為那該怎麼辦呢?搞不好你只能夠對正的座標物件進行加減操作,
任何返回的值也都應該是正的座標。所以現在的期望是這樣:

在這裡插入圖片描述

將輸出裡面負值的x和y替換成0,寫一個邊界檢查裝飾器!

在這裡插入圖片描述

10. 使用 @ 識別符號將裝飾器應用到函式

在這裡插入圖片描述

**11. *args and kwargs
在這裡插入圖片描述

第一個函式one只是簡單地講任何傳遞過來的位置引數全部打印出來而已,在程式碼#1處我們只是引用了函式內的變數args,
 *args僅僅只是用在函式定義的時候用來表示位置引數應該儲存在變數args裡面。Python允許我們制定一些引數並且通過
 args捕獲其他所有剩餘的未被捕捉的位置引數,就像#2處所示的那樣。
*操作符在函式被呼叫的時候也能使用,意義基本是一樣的。當呼叫一個函式的時候,一個用*標誌的變數意思是變數裡面的
內容需要被提取出來然後當做位置引數被使用。同樣的,來看個例子:

在這裡插入圖片描述

#1處的程式碼和#2處的程式碼所做的事情其實是一樣的,*args要麼是表示呼叫方法大的時候額外的引數可以從一個可迭代
列表中取得,要麼就是定義方法的時候標誌這個方法能夠接受任意的位置引數。
接下來提到的**會稍多更復雜一點,**代表著鍵值對的字典,和*所代表的意義相差無幾,也很簡單對不對:

在這裡插入圖片描述

當我們定義一個函式的時候,我們能夠用**kwargs來表明,所有未被捕獲的關鍵字引數都應該儲存在kwargs的字典中。
如前所訴,argshe kwargs並不是python語法的一部分,但在定義函式的時候,使用這樣的變數名算是一個不成文的約定。
和*一樣,我們同樣可以在定義或者呼叫函式的時候使用**。

在這裡插入圖片描述

12. 更通用的裝飾器–把日誌輸出到介面
在這裡插入圖片描述

我們定義的函式inner,它能夠接受任意數量和型別的引數並把它們傳遞給被包裝的方法,這讓我們能夠用這個裝飾器
來裝飾任何方法。隨便呼叫我們定義的哪個方法,相應的日誌也會列印到輸出視窗,和我們預期的一樣。