今天我們來學習下Python中的閉包。
什么是閉包
當我們在外部函數(shù)中定義了一個內部函數(shù)虎谢,并且內部函數(shù)能夠讀取到外部函數(shù)內的變量氏涩,這種函數(shù)我們就稱為閉包蚁鳖。簡單來說磺芭,閉包就是能夠讀取外部函數(shù)內的變量的函數(shù)。閉包的架子大概是這樣:
def demo_outer(x):
def demo_inner(y):
print("x的值:{}, y的值:{}, x + y 的值:{}".format(x, y, x + y))
return demo_inner
do = demo_outer(12)
do(34)
上面代碼執(zhí)行結果如下:
x的值:12, y的值:34, x + y 的值:46
上面的閉包代碼才睹,和我們之前學習的裝飾器類似徘跪,我們在外部函數(shù) demo_outer 下定義了一個內部函數(shù) demo_inner 甘邀,并且外部函數(shù)的返回值就是內部函數(shù)琅攘,同時在內部函數(shù)中,我們引用到了外部函數(shù)的變量 x 松邪,而閉包的作用就是可以將外層函數(shù)的變量保存在內存中而不被銷毀坞琴。
閉包的實例
我們先準備一個函數(shù)add(),每次調用該函數(shù)都只能傳一個數(shù) num 逗抑,每次返回的結果都是基于上一次結果的值 sum 進行累加操作剧辐。例如,sum的默認值為0邮府,如果我們依次調用 add(10)荧关、add(20)、add(30) 后褂傀,期望得到的最終結果是 sum = 60忍啤。
對于該問題,因為需要在函數(shù)內部對函數(shù)外部的變量進行處理仙辟,我們可能會考慮使用 global
來處理同波。
sum = 0
def get_add_sum(num):
global sum
sum += num
return sum
print(get_add_sum(10)) # 輸出:10
print(get_add_sum(20)) # 輸出:30
print(get_add_sum(30)) # 輸出:60
print(sum) # 輸出:60
上面代碼中鳄梅,我們在函數(shù)中通過全局變量 global
將 sum 聲明為全局變量,最終返回的結果也符合我們的期望未檩。但因為全局變量太靈活了戴尸,不同模塊函數(shù)都能自由訪問到全局變量,所以一般不推薦在函數(shù)內部中定義全局變量冤狡。
對于上面的問題孙蒙,除了使用全局變量外,我們還可以通過 閉包
來實現(xiàn)筒溃。
sum = 0
def get_add_sum(sum):
def add_num(num):
nonlocal sum
sum += num
return sum
return add_num
add = get_add_sum(sum)
print(add(10)) # 輸出:10
print(add(20)) # 輸出:30
print(add(30)) # 輸出:60
print(sum) # 輸出:0
在上面的閉包函數(shù)中马篮,定義了外層函數(shù)和內層嵌套函數(shù),執(zhí)行過程中怜奖,調用外層函數(shù) get_add_sum 返回的就是內層嵌套函數(shù) add_num 浑测,因為Python中函數(shù)也是對象,所以可以直接返回 add_num 歪玲。
而在內層嵌套函數(shù)中則實現(xiàn)了我們想要的累加操作迁央,在這里我們需使用關鍵字 nonlocal
來聲明外層函數(shù)中的變量,否則無法對外層函數(shù)中的變量進行修改滥崩,其作用域只在閉包函數(shù)里面岖圈,所以當我們在最后打印 sum 最終結果,輸出的仍然是其初始值 0 钙皮。
我們再來看一個例子:
def outer():
res = []
for i in range(3):
print("外部的i值:{}".format(i))
def inner(x):
print("內部的i值:{}".format(i))
return i + x
res.append(inner)
return res
temp = outer()
res = [i(10) for i in temp]
print(res)
上面代碼的執(zhí)行結果如下:
外部的i值:0
外部的i值:1
外部的i值:2
內部的i值:2
內部的i值:2
內部的i值:2
[12, 12, 12]
可以看到 res 的結果并不是 [10, 11, 12] 蜂科,因為 i 是外層函數(shù)的變量,并且其值是按 0短条,1导匣,2 變化,因為該函數(shù)中無法保存外層函數(shù)的變量茸时,故對于內部函數(shù)贡定,其實際只能訪問到最后外部變量的值 2 ,導致最終結果為:[12, 12, 12]可都。
在這里缓待,我們可以使用閉包來保存函數(shù)的外部變量,修改代碼如下:
def outer(i):
print("外部的i值:{}".format(i))
def inner(x):
print("內部的i值:{}".format(i))
return i + x
return inner
def demo():
res = []
for i in range(3):
res.append(outer(i))
return res
temp = demo()
res = [i(10) for i in temp]
print(res)
上面代碼的執(zhí)行結果如下:
外部的i值:0
外部的i值:1
外部的i值:2
內部的i值:0
內部的i值:1
內部的i值:2
[10, 11, 12]
閉包和裝飾器的區(qū)別
在Python中渠牲,閉包傳遞的參數(shù)是變量旋炒,裝飾器傳遞的參數(shù)是函數(shù)對象,它們只是在傳參內容上有不同签杈。那么裝飾器是不是屬于閉包的一種呢瘫镇,我們要怎么判斷一個函數(shù)是否是閉包呢?
我們可以打印 閉包 和 裝飾器 的屬性 __closure__
,如果一個函數(shù)是閉包汇四,那么查看該屬性將會返回一個cell對象組成的tuple對象接奈。
def demo_outer(x):
"""
閉包
"""
def demo_inner(y):
print("x的值:{}, y的值:{}, x + y 的值:{}".format(x, y, x + y))
return demo_inner
def demo_decorator(func):
"""
裝飾器
"""
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@demo_decorator
def method():
pass
do = demo_outer(5) # 閉包
print("閉包的屬性:{}".format(do.__closure__))
dd = demo_decorator(method) # 裝飾器
print("裝飾器的屬性:{}".format(dd.__closure__))
執(zhí)行結果如下:
閉包的屬性:(<cell at 0x0000023F48C3C8E8: int object at 0x00007FF92017D4A0>,)
裝飾器的屬性:(<cell at 0x0000023F48C3C8B8: function object at 0x0000023F48CB97B8>,)
所以結合上面的結果,我們也可以這樣理解:裝飾器本質上就是一個閉包函數(shù)通孽,它只是一個傳遞函數(shù)對象的閉包函數(shù)序宦。