1. 程式人生 > >一篇文章讓你明白python的裝飾器

一篇文章讓你明白python的裝飾器

pri 9.png 不同 概念 ota 一起 數字 上下文 讀取

在看閉包問題之前先來看看關於python中作用域的問題

變量作用域

技術分享圖片

對於上述代碼中出現錯誤,肯定沒什麽疑問了,畢竟b並沒有定義和賦值,當我們把代碼更改如下後:

技術分享圖片

再看一個例子:

技術分享圖片

首先這個錯誤已經非常明顯:說在賦值之前引用了局部變量b

可能很多人覺得會打印10然後打印6,其實這裏就是涉及到變量作用域的問題
當Python編譯函數的的定義體的時候,它判斷b是局部變量,畢竟在函數中有b = 9表示給b賦值了,所以python會從本地環境獲取b,當我們調用方法執行的時候,定義體會獲取並打印變量a的值,但是當嘗試獲取b的值的時候發現b沒有綁定值,所以要想讓上述代碼運行還可以把b設置為全局變量,或者把b賦值放到調用之前

技術分享圖片

技術分享圖片

函數對象的作用域

python中一切皆對象,同其他對象一樣,函數對象也有其使用的範圍即函數對象的作用域。
在python中我們通過def定義函數,函數對象的作用域與def所在的層級相同,
通過下面代碼進行理解:

def func1():
    def func2(x):
        return 2*x
    print(func2(5))

func1()
print(func2(5))

這個例子中我們在def func1函數內可以調用fun2,但是我們在外面是無法調用到func2的,所以結果為看到如下:

技術分享圖片

閉包

關於閉包主要有下面兩種說法:

  • 閉包是符合一定條件的函數,定義為:閉包是在其詞法上下文中引用了自由變量的函數
  • 閉包是由函數與其相關的引用環境組合而成的實體。定義為:在實現綁定時,需要創建一個能顯示表示引用環境的東西,並將它與相關的子程序捆綁在一起,這樣捆綁起來的整體稱為閉包

個人覺得第二種說法更準確,閉包只是在形式上表現像函數,實際不是函數。
我們對函數的定義是:一些可執行的代碼,這些代碼在函數定義後就確定了,不會在執行時發生變化,所以一個函數只有一個實例。

閉包在運行的時候可以有多個實例,不同的引用環境和相同的環境組合可以產生不同的實例。

這裏有一個詞:引用環境,其實引用環境就是在執行運行的某個時間點,所有處於活躍狀態的變量所組成的集合,這裏的變量是指變量的名字和其所代表的對象之間的聯系。

可以使用閉包語言的特點:

  • 函數可以作為另外一個函數的返回值或者參數,還可以作為一個變量的值。
  • 函數可以嵌套使用

而認為閉包是函數的有一句話是:
閉包是指延伸了作用域的函數,其中包含函數定義體中引用。但是不在定義體中定義的非全局變量。

上面這種說法個人覺得也是一種理解方式

相信看了這些概念也還是不好理解,還是通過下面例子更好理解:

先實現一種計算平均值的方法:

技術分享圖片

從結果我們可以看出這裏保存了每次的歷史值
換一種方法實現:

技術分享圖片

實現了第一種相同的效果,對這種方法分析:
通常我們會認為我們調用avg(10)的時候make_averager函數已經返回了,而它的本地作用域也一去不復返,但這裏其實series是自由變量,是指未在本地作用域綁定的變量
我們可以通過print(dir(avg)),看到如下結果:

技術分享圖片

其實這裏面保存著均布變量和自由變量的名稱,我們可以通過下面方法查看:

技術分享圖片

eries的綁定在返回的avg函數的__closure__屬性中這或許就是有的人會認為閉包一種函數。閉包會保留定義函數時存在的自由變量的綁定,這樣調用函數時雖然定義作用域不能用了,但是仍能使用那些綁定

關於nonlocal

剛開始了解閉包之後,如果嘗試使用這種編程方式容易出現以下錯誤使用例子:

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        count += 1
        total += new_value
        return total / count
    return averager

先來看一下錯誤提示:

技術分享圖片

這個例子中和我們上面使用的不同之處是:這裏的count和total是數字,是不可變類型,而之前的例子中series是一個列表是可變類型
所以這裏重新回到了最開始說的作用域問題了,當我們在averager中使用
count += 1的時候其實就是count = count + 1,這樣就是在averager函數定義體中對count進行賦值,count就變成了局部變量。

問題小結:當時數字,字符串,元組等不可變類型時,只能讀取不能更新,如果使用類似count += 1就會隱式的把count變成局部變量,所以開始例子中使用series,我們後面的操作是append並且列表還是可變對象

不過python3引入了一個新的關鍵詞nonlocal,通過它把變量標記為自由變量,這樣我們把上面這個錯誤的例子簡單更改:

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        nonlocal count,total
        count += 1
        total += new_value
        return total / count
    return averager

到這裏裝飾器的前奏就說完了,下面就是裝飾器,我個人覺得裝飾器只是閉包的一種應用,閉包在很多情況下都是一種非常好的變成技巧

裝飾器

關於裝飾器本來是想重新整理一下,看了自己之前整理的博客,已經挺詳細的,就把連接直接放這裏了
http://www.pythonsite.com/?p=113

一篇文章讓你明白python的裝飾器