Python: 攜帶狀態的閉包

zhengjie 8年前發布 | 5K 次閱讀 閉包 Python Python開發

在 Python 中,函數也是一個對象。因此,我們在定義函數時,可以再嵌套定義一個函數,并將該嵌套函數返回,比如:

frommathimportpow
 
defmake_pow(n):
    definner_func(x):    # 嵌套定義了 inner_func
        return pow(x, n)  # 注意這里引用了外部函數的 n
    return inner_func      # 返回 inner_func

上面的代碼中,函數 make_pow 里面又定義了一個內部函數 inner_func ,然后將該函數返回。因此,我們可以使用 make_pow 來生成另一個函數:

>>> pow2 = make_pow(2)  # pow2 是一個函數,參數 2 是一個自由變量
>>> pow2
<functioninner_funcat 0x10271faa0>
>>> pow2(6)
36.0

我們還注意到,內部函數 inner_func 引用了外部函數 make_pow 的自由變量 n ,這也就意味著,當函數 make_pow 的生命周期結束之后, n 這個變量依然會保存在 inner_func 中,它被 inner_func 所引用。

>>> del make_pow        # 刪除 make_pow
>>> pow3 = make_pow(3)
Traceback (mostrecentcalllast):
  File "<stdin>", line 1, in <module>
NameError: name 'make_pow' is not defined
>>> pow2(9)    # pow2 仍可正常調用,自由變量 2 仍保存在 pow2 中
81.0

像上面這種情況,一個函數返回了一個內部函數,該內部函數引用了外部函數的相關參數和變量,我們把該返回的內部函數稱為 閉包(Closure)

在上面的例子中, inner_func 就是一個閉包,它引用了自由變量 n 。

閉包的作用

  • 閉包的最大特點就是引用了自由變量,即使生成閉包的環境已經釋放,閉包仍然存在;
  • 閉包在運行時可以有多個實例,即使傳入的參數相同,比如:
>>> pow_a = make_pow(2)
>>> pow_b = make_pow(2)
>>> pow_a == pow_b
False
  • 利用閉包,我們還可以模擬類的實例。

這里構造一個類,用于求一個點到另一個點的距離:

frommathimportsqrt
 
class Point(object):
    def__init__(self, x, y):
        self.x, self.y = x, y
 
    defget_distance(self, u, v):
        distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
        return distance
 
>>> pt = Point(7, 2)        # 創建一個點
>>> pt.get_distance(10, 6)  # 求到另一個點的距離
5.0

用閉包來實現:

defpoint(x, y):
    defget_distance(u, v):
        return sqrt((x - u) ** 2 + (y - v) ** 2)
 
    return get_distance
 
>>> pt = point(7, 2)
>>> pt(10, 6)
5.0

可以看到,結果是一樣的,但使用閉包實現比使用類更加簡潔。

常見誤區

閉包的概念很簡單,但實現起來卻容易出現一些誤區,比如下面的例子:

defcount():
    funcs = []
    for i in [1, 2, 3]:
        def f():
            return i
        funcs.append(f)
    return funcs

在該例子中,我們在每次 for 循環中創建了一個函數,并將它存到 funcs 中。現在,調用上面的函數,你可能認為返回結果是 1, 2, 3,事實上卻不是:

>>> f1, f2, f3 = count()
>>> f1()
3
>>> f2()
3
>>> f3()
3

為什么呢?原因在于上面的函數 f 引用了變量 i ,但函數 f 并非立刻執行,當 for 循環結束時,此時變量 i 的值是3, funcs 里面的函數引用的變量都是 3,最終結果也就全為 3。

因此,我們應 盡量避免在閉包中引用循環變量,或者后續會發生變化的變量 。

那上面這種情況應該怎么解決呢?我們可以再創建一個函數,并將循環變量的值傳給該函數,如下:

defcount():
    funcs = []
    for i in [1, 2, 3]:
        def g(param):
            f = lambda : param    # 這里創建了一個匿名函數
            return f
        funcs.append(g(i))        # 將循環變量的值傳給 g
    return funcs
 
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
2
>>> f3()
3

小結

  • 閉包是攜帶自由變量的函數,即使創建閉包的外部函數的生命周期結束了,閉包所引用的自由變量仍會存在。
  • 閉包在運行可以有多個實例。
  • 盡量不要在閉包中引用循環變量,或者后續會發生變化的變量。

參考資料

 

來自:http://python.jobbole.com/86846/

 

 本文由用戶 zhengjie 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!