我們來實現一個可變參數的求和。通常情況下,求和的函數是這樣定義的:
def calc_sum(*args):
ax=0for n inargs:
ax= ax +nreturn ax
但是,如果不需要立刻求和,而是在后面的代碼中,根據需要再計算怎么辦?可以不返回求和的結果,而是返回求和的函數:
def lazy_sum(*args):defsum():
ax=0for n inargs:
ax= ax +nreturnaxreturnsum
f= lazy_sum(1, 2, 3, 4)print(f)print(f())
以上代碼,輸出:
.sum at 0x1014476a8>
10
當我們調用lazy_sum()時,返回的并不是求和結果,而是求和函數,調用函數f時,才真正計算求和的結果。
在這個例子中,我們在函數lazy_sum中又定義了函數sum,并且,內部函數sum可以引用外部函數lazy_sum的參數和局部變量,當lazy_sum返回函數sum時,相關參數和變量都保存在返回的函數中,這種稱為“閉包(Closure)”的程序結構擁有極大的威力。
請再注意一點,當我們調用lazy_sum()時,每次調用都會返回一個新的函數,即使傳入相同的參數:
def lazy_sum(*args):defsum():
ax=0for n inargs:
ax= ax +nreturnaxreturnsum
f1= lazy_sum(1, 2, 3, 4)
f2= lazy_sum(1, 2, 3, 4)print('f1==f2 :', f1 == f2)
以上代碼,輸出:
f1==f2 : False
f1()和f2()的調用結果互不影響。
閉包(closure)是函數式編程的重要的語法結構。下面看一個閉包的實際例子:
defline_conf(a, b):defline(x):return a * x +breturnline
line1= line_conf(1, 2)
line2= line_conf(3, 7)print(line1(5), line2(5))
這個例子中,函數line與環境變量a,b構成閉包。在創建閉包的時候,我們通過line_conf的參數a,b說明了這兩個環境變量的取值,這樣,我們就確定了函數的最終形式(y = x + 2和y = 3x + 7)。我們只需要變換參數a,b,就可以獲得不同的直線表達函數。由此,我們可以看到,閉包也具有提高代碼可復用性的作用。
如果沒有閉包,我們需要每次創建直線函數的時候同時說明a,b,x。這樣,我們就需要更多的參數傳遞,也減少了代碼的可移植性。利用閉包,我們實際上創建了泛函。line函數定義一種廣泛意義的函數。這個函數的一些方面已經確定(必須是直線),但另一些方面(比如a和b參數待定)。隨后,我們根據line_conf傳遞來的參數,通過閉包的形式,將最終函數確定下來。
閉包與并行運算
閉包有效的減少了函數所需定義的參數數目。這對于并行運算來說有重要的意義。在并行運算的環境下,我們可以讓每臺電腦負責一個函數,然后將一臺電腦的輸出和下一臺電腦的輸入串聯起來。最終,我們像流 水線一樣工作,從串聯的電腦集群一端輸入數據,從另一端輸出數據。這樣的情境最適合只有一個參數輸入的函數。閉包就可以實現這一目的。這也是函數式編程又熱起來的一個重要原因。函數式編程早在1950年代就已經存在,但應用并不廣泛。然而,我們上面描述的流水線式的工作并行集群過程,正適合函數式編程。由于函數式編程這一天然優勢,越來越多的語言也開始加入對函數式編程范式的支持。
需要注意的問題是,返回的函數并沒有立刻執行,而是直到調用了f()才執行。我們來看一個例子:
defcount():
fs=[]for i in range(1, 4):deff():return i*i
fs.append(f)returnfs
f1, f2, f3=count()print('f1():', f1())print('f2():', f2())print('f3():', f3())
在上面的例子中,每次循環,都創建了一個新的函數,然后,把創建的3個函數都返回了。
你可能認為調用f1(),f2()和f3()結果應該是1,4,9,但實際結果是:
f1(): 9f2():9f3():9
全部都是9,原因就在于返回的函數引用了變量i,但它并非立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,因此最終結果為9。
返回函數不要引用任何循環變量,或者后續會發生變化的變量。如果一定要引用循環變量怎么辦?方法是再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量后續如何更改,已綁定到函數參數的值不變:
defcount():deff(j):defg():return j *jreturng
fs=[]for i in range(1, 4):
fs.append(f(i))#f(i)立刻被執行,因此i的當前值被傳入f()
returnfs
f1, f2, f3=count()print('f1():', f1())print('f2():', f2())print('f3():', f3())
以上代碼,輸出:
f1(): 1f2():4f3():9