Python深入閉包

來源:互聯網
上載者:User

作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!

 

閉包(closure)是函數式編程的重要的文法結構。函數式編程是一種編程範式 (而面向過程編程和物件導向編程也都是編程範式)。在面向過程編程中,我們見到過函數(function);在物件導向編程中,我們見過對象(object)。函數和對象的根本目的是以某種邏輯方式組織代碼,並提高代碼的可重複使用性(reusability)。閉包也是一種組織代碼的結構,它同樣提高了代碼的可重複使用性。

不同的語言實現閉包的方式不同。Python以函數對象為基礎,為閉包這一文法結構提供支援的 (我們在特殊方法與多範式中,已經多次看到Python使用對象來實現一些特殊的文法)。Python一切皆對象,函數這一文法結構也是一個對象。在函數對象中,我們像使用一個普通對象一樣使用函數對象,比如更改函數對象的名字,或者將函數對象作為參數進行傳遞。

 

函數對象的範圍

和其他對象一樣,函數對象也有其存活的範圍,也就是函數對象的範圍。函數對象是使用def語句定義的,函數對象的範圍與def所在的層級相同。比如下面代碼,我們在line_conf函數的隸屬範圍內定義的函數line,就只能在line_conf的隸屬範圍內調用。

 

def line_conf():

    def line(x):

        return 2*x+1

    print(line(5))   # within the scope

 

 

line_conf()

print(line(5))       # out of the scope

 

line函數定義了一條直線(y = 2x + 1)。可以看到,在line_conf()中可以調用line函數,而在範圍之外調用line將會有下面的錯誤:

NameError: name 'line' is not defined

說明這時已經在範圍之外。

 

同樣,如果使用lambda定義函數,那麼函數對象的範圍與lambda所在的層級相同。

 

閉包

函數是一個對象,所以可以作為某個函數的返回結果。

 

def line_conf():

    def line(x):

        return 2*x+1

    return line       # return a function object

 

my_line = line_conf()

print(my_line(5))      

 

上面的代碼可以成功運行。line_conf的返回結果被賦給line對象。上面的代碼將列印11。

 

如果line()的定義中引用了外部的變數,會發生什麼呢?

 

def line_conf():

    b = 15

    def line(x):

        return 2*x+b

    return line       # return a function object

 

b = 5
my_line = line_conf()

print(my_line(5))      

 

我們可以看到,line定義的隸屬程式塊中引用了高層級的變數b,但b資訊存在於line的定義之外 (b的定義並不在line的隸屬程式塊中)。我們稱b為line的環境變數。事實上,line作為line_conf的傳回值時,line中已經包括b的取值(儘管b並不隸屬於line)。

上面的代碼將列印25,也就是說,line所參照的b值是函數對象定義時可供參考的b值,而不是使用時的b值。

 

一個函數和它的環境變數合在一起,就構成了一個閉包(closure)。在Python中,所謂的閉包是一個包含有環境變數取值的函數對象。環境變數取值被儲存在函數對象的__closure__屬性中。比如下面的代碼:

 

def line_conf():

    b = 15

    def line(x):

        return 2*x+b

    return line       # return a function object

 

b = 5

my_line = line_conf()

print(my_line.__closure__)

print(my_line.__closure__[0].cell_contents)

 

__closure__裡包含了一個元組(tuple)。這個元組中的每個元素是cell類型的對象。我們看到第一個cell包含的就是整數15,也就是我們建立閉包時的環境變數b的取值。

 

下面看一個閉包的實際例子:

 

def line_conf(a, b):

    def line(x):

        return ax + b

    return line

 

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)

print(line1(5), line2(5))

 

這個例子中,函數line與環境變數a,b構成閉包。在建立閉包的時候,我們通過line_conf的參數a,b說明了這兩個環境變數的取值,這樣,我們就確定了函數的最終形式(y = x + 1和y = 4x + 5)。我們只需要變換參數a,b,就可以獲得不同的直線表達函數。由此,我們可以看到,閉包也具有提高代碼可複用性的作用。

如果沒有閉包,我們需要每次建立直線函數的時候同時說明a,b,x。這樣,我們就需要更多的參數傳遞,也減少了代碼的可移植性。利用閉包,我們實際上建立了泛函。line函數定義一種廣泛意義的函數。這個函數的一些方面已經確定(必須是直線),但另一些方面(比如a和b參數待定)。隨後,我們根據line_conf傳遞來的參數,通過閉包的形式,將最終函數確定下來。

 

閉包與並行運算

閉包有效減少了函數所需定義的參數數目。這對於並行運算來說有重要的意義。在並行運算的環境下,我們可以讓每台電腦負責一個函數,然後將一台電腦的輸出和下一台電腦的輸入串聯起來。最終,我們像流水線一樣工作,從串聯的電腦叢集一端輸入資料,從另一端輸出資料。這樣的情境最適合只有一個參數輸入的函數。閉包就可以實現這一目的。

並行運算正稱為一個熱點。這也是函數式編程又熱起來的一個重要原因。函數式編程早在1950年代就已經存在,但應用並不廣泛。然而,我們上面描述的流水線式的工作並行叢集過程,正適合函數式編程。由於函數式編程這一天然優勢,越來越多的語言也開始加入對函數式編程範式的支援。

作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.