閉包(closure)是函式式程式設計的重要的語法結構。函式式程式設計是一種程式設計正規化 (而面向過程程式設計和物件導向程式設計也都是程式設計正規化)。在面向過程程式設計中,我們見到過函式(function);在物件導向程式設計中,我們見過物件(object)。函式和物件的根本目的是以某種邏輯方式組織**,並提高**的可重複使用性(reusability)。閉包也是一種組織**的結構,它同樣提高了**的可重複使用性。
不同的語言實現閉包的方式不同。python以函式物件為基礎,為閉包這一語法結構提供支援的 (我們在
特殊方法與多正規化
中,已經多次看到python使用物件來實現一些特殊的語法)。python一切皆物件,函式這一語法結構也是乙個物件。在
函式物件
中,我們像使用乙個普通物件一樣使用函式物件,比如更改函式物件的名字,或者將函式物件作為引數進行傳遞。
和其他物件一樣,函式物件也有其存活的範圍,也就是函式物件的作用域。函式物件是使用def語句定義的,函式物件的作用域與def所在的層級相同。比如下面**,我們在line_conf函式的隸屬範圍內定義的函式line,就只能在line_conf的隸屬範圍內呼叫。
defline函式定義了一條直線(y = 2x + 1)。可以看到,在line_conf()中可以呼叫line函式,而在作用域之外呼叫line將會有下面的錯誤:line_conf():
defline(x):
return 2*x+1
print(line(5)) #
within the scope
line_conf()
print(line(5)) #
out of the scope
nameerror: name 'line' is not defined說明這時已經在作用域之外。
同樣,如果使用lambda定義函式,那麼函式物件的作用域與lambda所在的層級相同。
函式是乙個物件,所以可以作為某個函式的返回結果。
def上面的**可以成功執行。line_conf的返回結果被賦給line物件。上面的**將列印11。line_conf():
defline(x):
return 2*x+1
return
line # return a function object
my_line =line_conf()
print(my_line(5))
如果line()的定義中引用了外部的變數,會發生什麼呢?
def我們可以看到,line定義的隸屬程式塊中引用了高層級的變數b,但b資訊存在於line的定義之外 (b的定義並不在line的隸屬程式塊中)。我們稱b為line的環境變數。事實上,line作為line_conf的返回值時,line中已經包括b的取值(儘管b並不隸屬於line)。line_conf():
b = 15
defline(x):
return 2*x+b
return line #
return a function object
b = 5
my_line =line_conf()
print(my_line(5))
上面的**將列印25,也就是說,line所參照的b值是函式物件定義時可供參考的b值,而不是使用時的b值。
乙個函式和它的環境變數合在一起,就構成了乙個閉包(closure)。在python中,所謂的閉包是乙個包含有環境變數取值的函式物件。環境變數取值被儲存在函式物件的__closure__屬性中。比如下面的**:
def__closure__裡包含了乙個元組(tuple)。這個元組中的每個元素是cell型別的物件。我們看到第乙個cell包含的就是整數15,也就是我們建立閉包時的環境變數b的取值。line_conf():
b = 15
defline(x):
return 2*x+b
return line #
return a function object
b = 5my_line =line_conf()
print(my_line.__closure__
)print(my_line.__closure__[0].cell_contents)
下面看乙個閉包的實際例子:
def這個例子中,函式line與環境變數a,b構成閉包。在建立閉包的時候,我們通過line_conf的引數a,b說明了這兩個環境變數的取值,這樣,我們就確定了函式的最終形式(y = x + 1和y = 4x + 5)。我們只需要變換引數a,b,就可以獲得不同的直線表達函式。由此,我們可以看到,閉包也具有提高**可復用性的作用。line_conf(a, b):
defline(x):
return a*x +b
return
line
line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5), line2(5))
如果沒有閉包,我們需要每次建立直線函式的時候同時說明a,b,x。這樣,我們就需要更多的引數傳遞,也減少了**的可移植性。利用閉包,我們實際上建立了泛函。line函式定義一種廣泛意義的函式。這個函式的一些方面已經確定(必須是直線),但另一些方面(比如a和b引數待定)。隨後,我們根據line_conf傳遞來的引數,通過閉包的形式,將最終函式確定下來。
閉包有效的減少了函式所需定義的引數數目。這對於並行運算來說有重要的意義。在並行運算的環境下,我們可以讓每台電腦負責乙個函式,然後將一台電腦的輸出和下一台電腦的輸入串聯起來。最終,我們像流水線一樣工作,從串聯的電腦集群一端輸入資料,從另一端輸出資料。這樣的情境最適合只有乙個引數輸入的函式。閉包就可以實現這一目的。
並行運算正稱為乙個熱點。這也是函式式程式設計又熱起來的乙個重要原因。函式式程式設計早在2023年代就已經存在,但應用並不廣泛。然而,我們上面描述的流水線式的工作並行集群過程,正適合函式式程式設計。由於函式式程式設計這一天然優勢,越來越多的語言也開始加入對函式式程式設計正規化的支援。
Python深入04 閉包
閉包 closure 是函式式程式設計的重要的語法結構。函式式程式設計是一種程式設計正規化 而面向過程程式設計和物件導向程式設計也都是程式設計正規化 在面向過程程式設計中,我們見到過函式 function 在物件導向程式設計中,我們見過物件 object 函式和物件的根本目的是以某種邏輯方式組織 並...
Python深入04 閉包
python深入04 閉包 閉包 closure 是函式式程式設計的重要的語法結構。函式式程式設計是一種程式設計正規化 而面向過程程式設計和物件導向程式設計也都是程式設計正規化 在面向過程程式設計中,我們見到過函式 function 在物件導向程式設計中,我們見過物件 object 函式和物件的根本...
Python 深入理解閉包
函式物件是使用def語句定義的,函式物件的作用域與def所在的層級相同。比如下面 我們在line conf函式的隸屬範圍內定義的函式line,就只能在line conf的隸屬範圍內呼叫。def line conf def line x return 2 x 1 print line 5 within...