在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 閉包
基礎(chǔ)
itertools
HTTP 服務(wù)
hashlib
閉包
文件和目錄
單元測試
使用 @property
標(biāo)準(zhǔn)模塊
陌生的 metaclass
Base64
進(jìn)程、線程和協(xié)程
讀寫二進(jìn)制文件
匿名函數(shù)
輸入和輸出
Click
元組
字符編碼
partial 函數(shù)
參考資料
collections
協(xié)程
類和實(shí)例
Python 之旅
定制類和魔法方法
常用數(shù)據(jù)類型
繼承和多態(tài)
ThreadLocal
HTTP 協(xié)議簡介
Requests 庫的使用
讀寫文本文件
列表
os 模塊
迭代器 (Iterator)
正則表達(dá)式
集合
上下文管理器
異常處理
你不知道的 super
定義函數(shù)
datetime
資源推薦
字典
slots 魔法
hmac
第三方模塊
進(jìn)程
類方法和靜態(tài)方法
函數(shù)參數(shù)
高階函數(shù)
函數(shù)
re 模塊
高級(jí)特性
線程
argparse
生成器
結(jié)束語
字符串
map/reduce/filter
函數(shù)式編程
Celery
裝飾器

閉包

在 Python 中,函數(shù)也是一個(gè)對(duì)象。因此,我們?cè)诙x函數(shù)時(shí),可以再嵌套定義一個(gè)函數(shù),并將該嵌套函數(shù)返回,比如:

from math import pow

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

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

>>> pow2 = make_pow(2)  # pow2 是一個(gè)函數(shù),參數(shù) 2 是一個(gè)自由變量
>>> pow2
<function inner_func at 0x10271faa0>
>>> pow2(6)
36.0

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

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

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

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

閉包的作用

  • 閉包的最大特點(diǎn)就是引用了自由變量,即使生成閉包的環(huán)境已經(jīng)釋放,閉包仍然存在。
  • 閉包在運(yùn)行時(shí)可以有多個(gè)實(shí)例,即使傳入的參數(shù)相同。
>>> pow_a = make_pow(2)
>>> pow_b = make_pow(2)
>>> pow_a == pow_b
False
  • 利用閉包,我們還可以模擬類的實(shí)例。

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

from math import sqrt

class Point(object):
    def __init__(self, x, y):
        self.x, self.y = x, y

    def get_distance(self, u, v):
        distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
        return distance

>>> pt = Point(7, 2)        # 創(chuàng)建一個(gè)點(diǎn)
>>> pt.get_distance(10, 6)  # 求到另一個(gè)點(diǎn)的距離
5.0

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

def point(x, y):
    def get_distance(u, v):
        return sqrt((x - u) ** 2 + (y - v) ** 2)

    return get_distance

>>> pt = point(7, 2)
>>> pt(10, 6)
5.0

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

常見誤區(qū)

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

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

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

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

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

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

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

def count():
    funcs = []
    for i in [1, 2, 3]:
        def g(param):
            f = lambda : param    # 這里創(chuàng)建了一個(gè)匿名函數(shù)
            return f
        funcs.append(g(i))        # 將循環(huán)變量的值傳給 g
    return funcs

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

小結(jié)

  • 閉包是攜帶自由變量的函數(shù),即使創(chuàng)建閉包的外部函數(shù)的生命周期結(jié)束了,閉包所引用的自由變量仍會(huì)存在。
  • 閉包在運(yùn)行可以有多個(gè)實(shí)例。
  • 盡量不要在閉包中引用循環(huán)變量,或者后續(xù)會(huì)發(fā)生變化的變量。

參考資料