一、定義
閉包(Closure) 指的是一個函數對象,即使其外部作用域的變量已經不存在了,仍然能訪問這些變量。簡單來說,閉包是由函數及其相關的環境變量組成的實體。
def outer():x = 10def inner():print(x)return innerf = outer()
f() # 輸出10
對于這個示例,我們可以將inner函數稱為閉包,它滿足了嵌套函數,它滿足了使用外部函數outer的參數x,外部函數的返回值滿足了是inner函數本身
二、閉包
(1)閉包的形成條件
閉包必須是一個嵌套函數,并且內部函數引用了外的變量,外部函數的返回值是該內部函數,對于這個引用外部函數變量的內部函數稱為閉包。
- 必須有嵌套函數(函數內部定義函數)
- 內部函數引用了外部函數的變量
- 外部函數返回內部函數對象
示例1:沒有嵌套函數,只有單層函數
def func():x = 10print(x)func()
示例2:嵌套函數存在,但內部函數沒有引用外部變量
def outer():x = 10def inner():print("Hello")return innerf = outer()
f()
示例3:嵌套函數引用了外部變量,但外部函數沒有返回該內部函數
def outer():x = 10def inner():print(x)inner() # 調用內部函數,但沒有返回它outer()
(2)閉包的使用場景
1)數據隱藏和封裝:閉包可以把一些變量藏在函數里面,不讓外面直接訪問,這樣就不會亂用或誤改,避免弄亂全局變量,讓代碼更安全。
2)裝飾器實現:裝飾器本質上依賴閉包機制
3)函數工廠:閉包可以幫你根據不同需求,快速生成帶有特定設置或環境的函數,就像工廠按訂單生產不同產品一樣。
def make_multiplier(factor):# 這是一個函數工廠,傳入一個倍數 factordef multiplier(number):# multiplier 是閉包,記住了外面的 factorreturn number * factorreturn multiplier# 生成一個把數字乘以3的函數
times3 = make_multiplier(3)
print(times3(5)) # 輸出 15# 生成一個把數字乘以10的函數
times10 = make_multiplier(10)
print(times10(5)) # 輸出 50
(3)閉包的作用與優勢
1)減少全局變量使用,提升代碼安全性
閉包讓變量“藏”在函數里,避免把變量放到全局,減少沖突和錯誤,讓代碼更可靠。
2)保持函數運行環境,方便管理狀態
閉包可以記住外部變量的值,即使外部函數已經結束,內部函數還能繼續用這些數據,方便管理和維護程序狀態。
三、Python中的可變類型與不可變類型
3.1 變量類型
(1)不可變類型
包括:int、float、str、tuple、frozenset等,對象一旦創建,值不能被改變,任何修改都會生成新對象。
(2)可變類型
包括:list、dict、set、自定義類對象等,對象創建后,內容可以被修改,地址不變。
3.2 兩者區別與內存表現
1)不可變類型變量修改時,實際是創建了新的對象,變量指向新地址
a = 10
print(id(a)) # 假設輸出:140703079708016a = a + 1
print(id(a)) # 輸出:140703079708048 (地址發生變化)
說明:變量 a
原來指向值為10的對象,修改后指向了新創建的值為11的對象,地址發生變化。
2)可變類型變量修改時,變量指向的對象地址不變,內容發生變化
lst = [1, 2, 3]
print(id(lst)) # 假設輸出:140703080123456lst.append(4)
print(id(lst)) # 輸出:140703080123456 (地址未變)
print(lst) # 輸出:[1, 2, 3, 4]
說明:變量 lst
指向的列表對象地址沒有變,但列表內部內容發生了變化。
3)通過id()
函數可以觀察變量地址變化。id()
?返回對象的內存地址標識,可以用來判斷變量是否指向同一個對象。
3.3 賦值和修改對變量的影響
1)對不可變類型變量賦值,變量綁定新對象,不影響原對象
2)對可變類型變量修改,直接改變對象內容,所有引用該對象的變量都能感知變化
四、閉包中變量的可變性影響
4.1 閉包對不可變類型變量的訪問與限制
閉包內部訪問外部不可變變量時,如果嘗試修改,會報錯(UnboundLocalError),因為修改會被當作局部變量賦值,導致訪問沖突。
def outer():x = 10 # 外部不可變變量def inner():x += 5 # 嘗試修改外部變量,報錯!print(x)inner()outer()
解釋:
在 inner 函數里,寫了 x += 5,這相當于想給 x 重新賦值。Python 看到這個,就把 x 當成是 inner 里的“新變量”,而不是外面那個已經有值的 x。但是這個“新變量”還沒被定義,結果你又想用它來計算,就出錯了。
簡單說,就是閉包里面如果你想修改外面那個數字,Python 會誤以為你是在用一個自己新建但還沒給值的變量,所以會報錯。要想修改外面的變量,需要告訴 Python “嘿,我用的是外面的那個變量”,這時候就得用 nonlocal。
4.2 使用 nonlocal 關鍵字修改不可變變量
nonlocal 的作用就是告訴 Python:“我想用的是外面函數里的那個變量,不是新建一個新的。”
所以在 inner 里寫了 nonlocal x,Python 就知道你要改的是外面 outer 函數里的 x,然后你給它加 5,修改成功了。這樣,inner 里和外面打印的 x 都變成了 15,因為它們指的是同一個變量。
def outer():x = 10def inner():nonlocal xx += 5print(x)inner()print(x)outer()
"""
輸出:
15
15
"""
4.3 閉包中對可變類型變量的修改與訪問
對可變類型變量,閉包內部可以直接修改其內容,無需nonlocal。當然建議使用nonlocal關鍵字聲明。
def outer():lst = [1, 2, 3]def inner():lst.append(4)print(lst)inner()print(lst)outer()
"""
輸出
[1, 2, 3, 4]
[1, 2, 3, 4]"""
為什么修改lst不需要使用nonlocal關鍵字呢。由于lst是一個列表他是一個可變類型,內層函數在對lst進行添加操作時,并沒有改變lst本身,它所指向的內存空間也沒有發生改變。如果內層函數代碼為del lst 想要刪除這個列表,也就是對外層函數的lst做了修改操作,此時就會發生報錯UnboundLocalError,如果加上關鍵字nonlocal進行聲明,就可以對它進行刪除。
4.4 常見誤區
1)不是所有閉包里的變量修改都要用 nonlocal
def outer():lst = [1, 2, 3] # 可變類型變量def inner():lst.append(4) # 直接修改列表內容,無需nonlocalprint(lst)inner()print(lst)outer()
2)別把“換變量”和“改內容”搞混了
def outer():x = 10 # 不可變類型lst = [1, 2, 3] # 可變類型def inner():# x += 1 # 重新綁定,會報錯,需要nonlocal# 改成下面這樣才合法:nonlocal xx += 1# 對列表內容修改,不是重新綁定lst.append(4)print(x, lst)inner()print(x, lst)outer()
3)別忽略了可變對象改了內容,閉包里的變量也跟著變
def outer():d = {'count': 0} # 可變字典def inner():d['count'] += 1 # 修改字典內容print(d['count'])return innerf = outer()
f() # 輸出1
f() # 輸出2