fun = [lambda x: x*i for i in range(4)] 本質解析/原理,LEGB規則 閉包原理


命名空間,閉包原理,參考點擊本文

一、問題描述

 

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

上述式子的輸出結果:
預計結果為:0, 2, 4, 6
實際輸出為: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)

#運行結果為:為了排版美觀,我已將輸出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的異常。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM