1. 程式人生 > >[lambda x: x*i for i in range(4)] 詳細解析+LEGB規則 閉包原理

[lambda x: x*i for i in range(4)] 詳細解析+LEGB規則 閉包原理

轉自:http://www.cnblogs.com/shiqi17/p/9608195.html

一、問題描述

fun = [lambda x: x*i for i in range(4)]
for item in fun:
    print(item(1))
    
上述式子的輸出結果:
預計結果為:0, 1, 2, 3
實際輸出為:3, 3, 3, 3

原理:i 在外層作用域

lambda x: x*i 為內層(嵌)函式,他的名稱空間中只有 {'x': 1} 沒有 i ,
所以執行時會向外層函式(這兒是列表解析式函式 [ ])的名稱空間中請求 i 
而當列表解析式執行時,列表解析式名稱空間中的 i 經過迴圈依次變化為 0->1->2->3 最後固定為 3 ,
所以當 lambda x: x*i 內層函式執行時,去外層函式取 i 每次都只能取到 3

解決辦法:變閉包作用域為區域性作用域

給內層函式 lambda x: x*i 增加引數,名稱空間中有了用來儲存每次的 i ,
即改成 [lambda x, i=i: x*i for i in range(4)] 這樣每一次,內部迴圈生成一個lambda 函式時,
都會把 --i--作為預設引數傳入lambda的名稱空間
迴圈4次實際lambda表示式為:
第一次:lambda x, i=0 第二次:lambda x, i=1 第三次:lambda x, i=2 第四次:lambda x, i=3

fun = [lambda x, i=i: x*i for i in range(4)]
for item in fun:
    print(item(1))

#輸出結果為:
0
1
2
3

二、上面看不懂就看這兒
函式fun = [lambda x: x*i for i in range(4)]等價於:如下函式

def func():
    fun_lambda_list = []

    for i in range(4):
        def lambda_(x):
            return x*i
        fun_lambda_list.append(lambda_)
        
    return fun_lambda_list

檢視該函式名稱空間及 I 值變化:

def func():
    fun_lambda_list = []
    for i in range(4):
        def lambda_(x):
            print('Lambda函式中 i {} 名稱空間為:{}:'.format(i, locals()))
            return x*i
        fun_lambda_list.append(lambda_)
        print('外層函式 I 為:{} 名稱空間為:{}'.format(i, locals()))

    return fun_lambda_list

fl = func()
fl[0](1)
fl[1](1)
fl[2](1)
fl[3](1)

#執行結果為:
外層函式 I 為:0 名稱空間為:{'lambda_': <function func.<locals>.lambda_ at 0x7f7324d04158>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x7f7324d04158>], 'i': 0}
外層函式 I 為:1 名稱空間為:{'lambda_': <function func.<locals>.lambda_ at 0x7f7324d041e0>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x7f7324d04158>, <function func.<locals>.lambda_ at 0x7f7324d041e0>], 'i': 1}
外層函式 I 為:2 名稱空間為:{'lambda_': <function func.<locals>.lambda_ at 0x7f7324d04268>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x7f7324d04158>, <function func.<locals>.lambda_ at 0x7f7324d041e0>, <function func.<locals>.lambda_ at 0x7f7324d04268>], 'i': 2}
外層函式 I 為:3 名稱空間為:{'lambda_': <function func.<locals>.lambda_ at 0x7f7324d042f0>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x7f7324d04158>, <function func.<locals>.lambda_ at 0x7f7324d041e0>, <function func.<locals>.lambda_ at 0x7f7324d04268>, <function func.<locals>.lambda_ at 0x7f7324d042f0>], 'i': 3}
Lambda函式中 i 3 名稱空間為:{'x': 2, 'i': 3}:
Lambda函式中 i 3 名稱空間為:{'x': 2, 'i': 3}:
Lambda函式中 i 3 名稱空間為:{'x': 2, 'i': 3}:
Lambda函式中 i 3 名稱空間為:{'x': 2, 'i': 3}:

為了排版美觀,我已將輸出lambda_函式地址改名為:lam函式1 2 3

外層函式I為:0 名稱空間為:{'i': 0, 'lambda_': lam函式1 'fun_lambda_list': [lam函式1]}
外I:1 命空:{'i': 1, 'lambda_': lam函式2, 'fun_lambda_list': [lam函式1, lam函式2]}
外I:2 命空:{'i': 2, 'lambda_': lam函式3, 'fun_lambda_list': [lam函式1, lam函式2, lam函式3]}
外I:3 命空:{'i': 3, 'lambda_': lam函式4, 'fun_lambda_list': [lam函式1, lam函式2, lam函式3, lam函式4]}
Lambda函式中 i 3 名稱空間為:{'i': 3, 'x': 1}:
Lambda函式中 i 3 名稱空間為:{'i': 3, 'x': 1}:
Lambda函式中 i 3 名稱空間為:{'i': 3, 'x': 1}:
Lambda函式中 i 3 名稱空間為:{'i': 3, 'x': 1}:

可以看見:就像上面所說的:四次迴圈中外層函式名稱空間中的 i 從 0–>1–>2–>3 最後固定為3,
而在此過程中內嵌函式-Lambda函式中因為沒有定義 i 所以只有Lambda 函式動態執行時,
在自己名稱空間中找不到 i 才去外層函式複製 i = 3 過來,結果就是所有lambda函式的 i 都為 3,
導致得不到預計輸出結果:0,1,2,3 只能得到 3, 3, 3, 3

解決辦法:變閉包作用域為區域性作用域。

def func():
    fun_lambda_list = []
    for i in range(4):
        def lambda_(x, i= i):
            print('Lambda函式中 i {} 名稱空間為:{}:'.format(i, locals()))
            return x*i
        fun_lambda_list.append(lambda_)
    return fun_lambda_list

fl = func()
res = []

res.append(fl[0](1))
res.append(fl[1](1))
res.append(fl[2](1))
res.append(fl[3](1))

print(res)

#輸出結果為:
Lambda函式中 i 0 名稱空間為:{'x': 1, 'i': 0}:
Lambda函式中 i 1 名稱空間為:{'x': 1, 'i': 1}:
Lambda函式中 i 2 名稱空間為:{'x': 1, 'i': 2}:
Lambda函式中 i 3 名稱空間為:{'x': 1, 'i': 3}:
[0, 1, 2, 3]

給內層函式 lambda_增加預設引數,名稱空間中有了用來儲存每次的 i , 即改成 def lambda_(x, i=i) : 這樣每一次,
內部迴圈生成一個lambda 函式時,都會把 i 作為預設引數傳入lambda的名稱空間
迴圈4次實際lambda表示式為:
第一次:lambda_( x, i=0) 第二次:lambda_(x, i=1) 第三次:lambda_(x, i=2) 第四次:lambda_(x, i=3)

這樣我們就能得到預計的結果:0, 1, 2, 3

LEGB
只有函式、類、模組會產生作用域,程式碼塊不會產生作用域。作用域按照變數的定義位置可以劃分為4類:

Local(函式內部)區域性作用域

Enclosing(巢狀函式的外層函式內部)巢狀作用域(閉包)

Global(模組全域性)全域性作用域

Built-in(內建)內建作用域

python直譯器查詢變數時,會按照順序依次查詢區域性作用域—>巢狀作用域—>全域性作用域—>內建作用域,在任意一個作用域中找到變數則停止查詢,所有作用域查詢完成沒有找到對應的變數,則丟擲 NameError: name ‘xxxx’ is not defined的異常。

人生還有意義。那一定是還在找存在的理由