python閉包問題
一. 閉包問題
-
閉包中的值儲存在返回函式的cell object中。
def fun_a(msg): def fun_b(): print("fun_b namespace msg is ", msg) return fun_b func =fun_a("hello") print(func.__closure__[0].cell_contents) >>> hello
-
func.__closure__
返回cell object組成的元組,每個cell object都是隻讀的,不可改變。func.__closure__[0].cell_contents = "world" >>> AttributeError: attribute 'cell_contents' of 'cell' objects is not writable
-
子函式改變父函式中的值:
-
如果在函式裡內函式裡面直接msg賦值時可以的,因為此時的msg是
func_b
的locals變數,和func_a
中的msg沒有關係,也改變不了func_a
中的變數def fun_a(msg): def fun_b(): msg = " world" print("fun_b namespace msg is ", msg) yield fun_b return msg
-
在func_b中沒有定義新的msg時,直接使用,但接下來改變msg的值,會報錯:UnboundLocalError: local variable ‘msg’ referenced before assignment
def fun_a(msg): def fun_b(): a = msg msg = " world" print("fun_b namespace msg is ", msg) yield fun_b return msg
-
只有在func_b使用外面的函式變數時,定義nonlocal,才可以改變外面函式的值,而且這個值是一個引用,更改後即使外面的函式,這個值也修改了。
def fun_a(msg): def fun_b(): nonlocal msg msg += " world" print("fun_b namespace msg is ", msg) yield fun_b return msg def main(): gen = fun_a("hello") msg = yield from gen print("fun_a namespace msg is :", msg) for func in main(): func()
結果
fun_b namespace msg is hello world fun_a namespace msg is : hello world
-
以上的原因:python local 變數定義規則:
-
當對一個作用域中的變數進行賦值時,該變數將成為該作用域的區域性變數,並在外部作用域中隱藏任何類似命名的變數,所以外部變數只可以讀,但是不可以賦值,賦值的話,就將變數當做本地變數,遮蔽了外部作用域的變量了,如果沒有定義的地方,會報錯。
-
所以在一個作用域中要改變全域性變數,必須用global宣告變數時全域性作用域的,而閉包函式需要nonlocals
-
使用外部變數導致的問題:
squares = [] for x in range(5): squares.append(lambda: x**2) >>> squares[2]() 16 >>> squares[4]() 16 >>> x = 8 >>> squares[2]() 64
-
x不是lambda函式的區域性變數,而是函式外邊的變數,x這個變數一直存在,lambda執行時才回去讀x的值,如果改變x的值,lambda的結果還會變:
>>> x = 8 >>> squares[2]() 64
-
函式的預設引數都是在定義時就已經初始化好的,所以會導致預設引數是列表的話,函式中會出現錯誤,而如果函式的引數是外部變數的話,只有當函式呼叫時,才會確定引數的值。
-
避免這個問題,只需要將外部變數變為本地變數,將外部變數賦值給一個本地變數。
>>> squares = [] >>> for x in range(5): ... squares.append(lambda n=x: n**2)
-
-
-
二. 將一個變數儲存到一個函式中
-
閉包
fn = (lambda x: lambda: x)(value)
這樣,fn所代表的的lambda 函式就綁定了value
-
偏函式
from functools import partial a = lambda x: x b = partial(a, 234) print(b, b(), b.args, b.func, b.keywords) 結果: functools.partial(<function <lambda> at 0x028FC6A8>, 234) 234 (234,) <function <lambda> at 0x028FC6A8> {}
使用偏函式可以達到同樣的目的,只不過偏函式屬於另外的型別,不再是簡單的函式,偏函式內部儲存了引數,不會出現閉包的問題。
連結: