傳回函數
高階函數除了可以接受函數作為參數外,還可以把函數作為結果值傳回。
例如:實作一個可變參數的求和。通常情況下,求和的函數是這樣定義的:
def calc_sum(*args): # calc ['kælk] 計算; calculate
ax = 0
for n in args:
ax = ax + n
return ax
print(calc_sum(1, 3, 5, 7, 9))
# 或者
def calc_sum(lst):
return sum(lst) # sum(iterable, start=0, /)
print(calc_sum([1, 3, 5, 7, 9]))
但是,如果不需要立刻求和,而是在後面的代碼中,根據需要再計算怎麼辦?可以不傳回求和的結果,而是傳回求和的函數,即傳回函數可以把一些計算延遲執行:
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
當我們調用 lazy_sum()時,傳回的并不是求和結果,而是求和函數:
f = lazy_sum(1, 3, 5, 7, 9)
print(f)
# 輸出結果
<function lazy_sum.<locals>.sum at 0x00000227705EC268>
調用函數 f 時,才真正計算求和的結果:
print(f())
#輸出結果
25
注:(1) 在這個例子中,我們在函數 lazy_sum 中又定義了函數 sum,并且,内部函數 sum 可以引用外部函數 lazy_sum 的參數和局部變量,當 lazy_sum 傳回函數 sum 時,相關參數和變量都儲存在傳回的函數中,這種稱為“閉包(Closure) ”的程式結構擁有極大的威力。
(2) 當我們調用 lazy_sum()時,每次調用都會傳回一個新的函數,即使傳入相同的參數:
f1= lazy_sum(1, 3, 5, 7, 9)
f2= lazy_sum(1, 3, 5, 7, 9)
print(f1 == f2)
#輸出結果
False
執行個體1. 請編寫一個函數calc_prod(lst),它接收一個list,傳回一個函數,傳回函數可以計算參數的乘積。
from functools import reduce #從functools子產品引入reduce()函數
def calc_prod(lst): # product 乘積;産品
def lazy_prod():
def f(x,y):
return x * y
return reduce(f, lst)
return lazy_prod
f = calc_prod([1, 2, 3, 4])
print (f())
#輸出結果
24
閉包
注意到傳回的函數在其定義内部引用了局部變量 args,是以,當一個函數傳回了一個函數後,其内部的局部變量還被新函數引用,是以,閉包用起來簡單,實作起來可不容易。
另一個需要注意的問題是,傳回的函數并沒有立刻執行,而是直到調用
了 f()才執行。我們來看一個例子:
# why f1(), f2(), f3() returns 9, 9, 9 rather than 1, 4, 9?
def count():
fs = []
for i in range(1, 4):
def f():
return i * i
fs.append(f)
return fs
f1, f2, f3 = count() # count()執行了函數,函數傳回了fs,fs是一個由三個函數組成的清單,即
print(f1) # fs = [f(i),f(i),f(i)],這個時候的f1,f2,f3隻是作為變量給這些函
print(f2) # 數一個名字而已。
print(f3)
#輸出結果
<function count.<locals>.f at 0x0000024878ECC268>
<function count.<locals>.f at 0x0000024878ECC268>
<function count.<locals>.f at 0x0000024878ECC268>
print(f1())
print(f2())
print(f3())
#輸出結果
9
9
9
注:全部都是 9!原因就在于傳回的函數引用了變量 i,但它并非立刻執行。等到 3 個函數都傳回時,它們所引用的變量 i 已經變成了 3,是以最終結果為 9。
總結:傳回閉包時牢記的一點就是:傳回函數不要引用任何循環變量, 或者後續會發生變化的變量。
如果一定要引用循環變量怎麼辦?方法是再建立一個函數,用該函數的參數綁定循環變量目前的值,無論該循環變量後續如何更改,已綁定到函數參數的值不變:
# fix:
def count():
fs = []
def f(n):
def j():
return n * n
return j
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被執行,是以 i 的目前值被傳入 f()
return fs
f1, f2, f3 = count()
print(f1)
print(f2)
print(f3)
#輸出結果
<function count.<locals>.f.<locals>.j at 0x0000029404F48E18>
<function count.<locals>.f.<locals>.j at 0x0000029404F48EA0>
<function count.<locals>.f.<locals>.j at 0x0000029404F48F28>
print(f1())
print(f2())
print(f3())
#輸出結果
1
4
9
注:缺點是代碼較長,可利用 lambda 函數縮短代碼。(待完成)