Python進階:攜帶狀態(tài)的閉包

以前也發(fā)過介紹閉包的文章,今天學(xué)習(xí)一下如何在閉包中攜帶狀態(tài)。

作者:Ethan?

原文:https://funhacks.net/2016/11/17/closure/

閉包

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

  1. from math import pow

  2. def make_pow(n):

  3. ? ?def inner_func(x): ? ? # 嵌套定義了 inner_func

  4. ? ? ? ?return pow(x, n) ? # 注意這里引用了外部函數(shù)的 n

  5. ? ?return inner_func ? ? ?# 返回 inner_func

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

  1. >> > pow2 = make_pow(2) ?# pow2 是一個函數(shù),參數(shù) 2 是一個自由變量

  2. >> > pow2

  3. <function inner_func at 0x10271faa0 >

  4. >> > pow2(6)

  5. 36.0

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

  1. >> > del make_pow ? ? ? ? # 刪除 make_pow

  2. >> > pow3 = make_pow(3)

  3. Traceback(most recent call last):

  4. ? ?File "<stdin>", line 1, in < module >

  5. NameError:

  6. ? ?name 'make_pow' is not defined

  7. >> > pow2(9) ? ? # pow2 仍可正常調(diào)用,自由變量 2 仍保存在 pow2 中

  8. 81.0

---|---

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

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

閉包的作用

  • 閉包的最大特點就是引用了自由變量,即使生成閉包的環(huán)境已經(jīng)釋放,閉包仍然存在;

  • 閉包在運行時可以有多個實例,即使傳入的參數(shù)相同,比如:

  1. >> > pow_a = make_pow(2)

  2. >> > pow_b = make_pow(2)

  3. >> > pow_a == pow_b

  4. False

  • 利用閉包,我們還可以模擬類的實例。

這里構(gòu)造一個類,用于求一個點到另一個點的距離:

  1. from math import sqrt

  2. class Point(object):

  3. ? ?def __init__(self, x, y):

  4. ? ? ? ?self.x, self.y = x, y

  5. ? ?def get_distance(self, u, v):

  6. ? ? ? ?distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)

  7. ? ? ? ?return distance

  8. >> > pt = Point(7, 2) ? ? ? ?# 創(chuàng)建一個點

  9. >> > pt.get_distance(10, 6) ?# 求到另一個點的距離

  10. 5.0

用閉包來實現(xiàn):

  1. def point(x, y):

  2. ? ?def get_distance(u, v):

  3. ? ? ? ?return sqrt((x - u) ** 2 + (y - v) ** 2)

  4. ? ?return get_distance

  5. >> > pt = point(7, 2)

  6. >> > pt(10, 6)

  7. 5.0

可以看到,結(jié)果是一樣的,但使用閉包實現(xiàn)比使用類更加簡潔。

常見誤區(qū)

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

  1. def count():

  2. ? ?funcs = []

  3. ? ?for i in [1, 2, 3]:

  4. ? ? ? ?def f():

  5. ? ? ? ? ? ?return i

  6. ? ? ? ?funcs.append(f)

  7. ? ?return funcs

在該例子中,我們在每次?for?循環(huán)中創(chuàng)建了一個函數(shù),并將它存到?funcs?中?,F(xiàn)在,調(diào)用上面的函數(shù),你可能認(rèn)為返回結(jié)果是 1, 2, 3,事實上卻不是:

  1. >> > f1, f2, f3 = count()

  2. >> > f1()

  3. 3

  4. >> > f2()

  5. 3

  6. >> > f3()

  7. 3

為什么呢?原因在于上面的函數(shù)?f?引用了變量?i?,但函數(shù)?f?并非立刻執(zhí)行,當(dāng)?for?循環(huán)結(jié)束時,此時變量?i?的值是3,?funcs?里面的函數(shù)引用的變量都是 3,最終結(jié)果也就全為 3。

因此,我們應(yīng)盡量避免在閉包中引用循環(huán)變量,或者后續(xù)會發(fā)生變化的變量。

那上面這種情況應(yīng)該怎么解決呢?我們可以再創(chuàng)建一個函數(shù),并將循環(huán)變量的值傳給該函數(shù),如下:

  1. def count():

  2. ? ?funcs = []

  3. ? ?for i in [1, 2, 3]:

  4. ? ? ? ?def g(param):

  5. ? ? ? ? ? ?f = lambda: param ? ?# 這里創(chuàng)建了一個匿名函數(shù)

  6. ? ? ? ? ? ?return f

  7. ? ? ? ?funcs.append(g(i)) ? ? ? ?# 將循環(huán)變量的值傳給 g

  8. ? ?return funcs

  9. >> > f1, f2, f3 = count()

  10. >> > f1()

  11. 1

  12. >> > f2()

  13. 2

  14. >> > f3()

  15. 3

小結(jié)

  • 閉包是攜帶自由變量的函數(shù),即使創(chuàng)建閉包的外部函數(shù)的生命周期結(jié)束了,閉包所引用的自由變量仍會存在。

  • 閉包在運行可以有多個實例。

  • 盡量不要在閉包中引用循環(huán)變量,或者后續(xù)會發(fā)生變化的變量。

參考資料


題圖:pexels,CC0 授權(quán)。

點擊閱讀原文,查看更多 Python 教程和資源。



閱讀原文:http://mp.weixin.qq.com/s?__biz=MzAwNDc0MTUxMw==&mid=2649639864&idx=1&sn=691fe0541a31045bd7f8d78969bafb16&scene=0#wechat_redirect
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 86.復(fù)合 Cases 共享相同代碼塊的多個switch 分支 分支可以合并, 寫在分支后用逗號分開。如果任何模式...
    無灃閱讀 1,543評論 1 5
  • 這兩天學(xué)習(xí)的時候?qū)ython中的閉包產(chǎn)生了興趣,網(wǎng)上這篇文章寫得很好,寫在這里大家看看。 1. 閉包的概念 首...
    Alistair閱讀 297評論 0 0
  • 閉包是功能性自包含模塊,可以在代碼中被傳遞和使用。Swift中的閉包與 C 和 Objective-C中的 blo...
    AirZilong閱讀 381評論 0 2
  • 1、引言 最近在刷leetcode題的時候,遇到一個求最長回文子串的題目,于是,我寫了如下的代碼: 哎呀,寫了兩個...
    文哥的學(xué)習(xí)日記閱讀 14,502評論 6 32
  • 這是一個兩男一女的故事,放了一半的愛情,加了三分的欲望,添了一點動蕩年代的不得已,熬成了一鍋叫宿命的粥,任你心高命...
    e7ca5e512f65閱讀 616評論 0 2

友情鏈接更多精彩內(nèi)容