## 問題來源
最近看到了一個python程序題,就三行代碼,卻思考了很久才考慮明白,決定分享一下。
~~~
def num():
return [lambda x:i*x for i in range(4)]
print([m(2) for m in num()])
~~~
預計結果為:0, 2, 4, 6
實際輸出為:6, 6, 6, 6
## 思路分析
其實把上面的代碼拆分一下,等價于下面的代碼
~~~
def func():
fun_lambda_list = []
for i in range(4):
def lamb(x):
return x*i
fun_lambda_list.append(lamb)
return fun_lambda_list
~~~
我們再把上面的代碼加兩行print輸出,讓結果看的更加明顯:
PS:**locals()**?函數會以字典類型返回當前位置的全部局部變量。
~~~
def func():
fun_lambda_list = []
for i in range(4):
def lamb(x):
print('Lambda函數中 i {} 命名空間為:{}:'.format(i, locals()))
return x*i
fun_lambda_list.append(lamb)
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 0x00000116B6837488>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x00000116B6837488>], 'i': 0}
外層函數 I 為:1 命名空間為:{'lambda_': <function func.<locals>.lambda_ at 0x00000116B6837510>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x00000116B6837488>, <function func.<locals>.lambda_ at 0x00000116B6837510>], 'i': 1}
外層函數 I 為:2 命名空間為:{'lambda_': <function func.<locals>.lambda_ at 0x00000116B6837598>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x00000116B6837488>, <function func.<locals>.lambda_ at 0x00000116B6837510>, <function func.<locals>.lambda_ at 0x00000116B6837598>], 'i': 2}
外層函數 I 為:3 命名空間為:{'lambda_': <function func.<locals>.lambda_ at 0x00000116B6837620>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x00000116B6837488>, <function func.<locals>.lambda_ at 0x00000116B6837510>, <function func.<locals>.lambda_ at 0x00000116B6837598>, <function func.<locals>.lambda_ at 0x00000116B6837620>], 'i': 3}
Lambda函數中 i 3 命名空間為:{'x': 1, 'i': 3}:
Lambda函數中 i 3 命名空間為:{'x': 1, 'i': 3}:
Lambda函數中 i 3 命名空間為:{'x': 1, 'i': 3}:
Lambda函數中 i 3 命名空間為:{'x': 1, 'i': 3}:
~~~
可以發現:四次循環中外層函數命名空間中的**?i?從****?0-->1-->2-->3?最后固定為****3**,而在此過程中內嵌函數lamb函數中因為沒有定義?i?所以只有lamb函數動態運行時,在自己命名空間中找不到?i?才去外層函數復制?i = 3?過來,結果就是所有lamb函數的?i?都為 3,導致得不到預計輸出結果:0,2,4,6 只能得到?6,6,6,6。
## 解決辦法
變閉包作用域為局部作用域
```
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_)
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 0x0000021F12227488>, 'i': 0, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x0000021F12227488>]}
外層函數 I 為:1 命名空間為:{'lambda_': <function func.<locals>.lambda_ at 0x0000021F12227510>, 'i': 1, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x0000021F12227488>, <function func.<locals>.lambda_ at 0x0000021F12227510>]}
外層函數 I 為:2 命名空間為:{'lambda_': <function func.<locals>.lambda_ at 0x0000021F12227598>, 'i': 2, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x0000021F12227488>, <function func.<locals>.lambda_ at 0x0000021F12227510>, <function func.<locals>.lambda_ at 0x0000021F12227598>]}
外層函數 I 為:3 命名空間為:{'lambda_': <function func.<locals>.lambda_ at 0x0000021F12227620>, 'i': 3, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x0000021F12227488>, <function func.<locals>.lambda_ at 0x0000021F12227510>, <function func.<locals>.lambda_ at 0x0000021F12227598>, <function func.<locals>.lambda_ at 0x0000021F12227620>]}
Lambda函數中 i 0 命名空間為:{'i': 0, 'x': 1}:
Lambda函數中 i 1 命名空間為:{'i': 1, 'x': 1}:
Lambda函數中 i 2 命名空間為:{'i': 2, 'x': 1}:
Lambda函數中 i 3 命名空間為:{'i': 3, 'x': 1}:
輸出結果
```
所以,再回到最開始的那段代碼。`lambda x: x*i`?為內層(嵌)函數,他的命名空間中只有 {'x': 1} 沒有?i?,所以運行時會向外層函數(這兒是列表解析式函數 \[ \])的命名空間中請求?i 。而當列表解析式運行時,列表解析式命名空間中的?i?經過循環依次變化為?0-->1-->2-->3?最后固定為?3?,所以當?`lambda x: x*i`?內層函數運行時,去外層函數取?i?每次都只能取到 3。
將代碼改成下面這樣輸出就會變成0,2,4,6.
~~~
def num():
return [lambda x,i=i:i*x for i in range(4)]
print([m(2) for m in num()])
~~~
## LEGB規則
只有函數、類、模塊會產生作用域,代碼塊不會產生作用域。作用域按照變量的定義位置可以劃分為4類:
~~~
Local(函數內部)局部作用域
Enclosing(嵌套函數的外層函數內部)嵌套作用域(閉包)
Global(模塊全局)全局作用域
Built-in(內建)內建作用域
~~~
python解釋器查找變量時,會按照順序依次查找**局部作用域--->嵌套作用域--->全局作用域--->內建作用域**,在任意一個作用域中找到變量則停止查找,所有作用域查找完成沒有找到對應的變量,則拋出 NameError: name 'xxxx' is not defined的異常。
- Python學習
- Python基礎
- Python初識
- 列表生成式,生成器,可迭代對象,迭代器詳解
- Python面向對象
- Python中的單例模式
- Python變量作用域、LEGB、閉包
- Python異常處理
- Python操作正則
- Python中的賦值與深淺拷貝
- Python自定義CLI三方庫
- Python并發編程
- Python之進程
- Python之線程
- Python之協程
- Python并發編程與IO模型
- Python網絡編程
- Python之socket網絡編程
- Django學習
- 反向解析
- Cookie和Session操作
- 文件上傳
- 緩存的配置和使用
- 信號
- FBV&&CBV&&中間件
- Django補充
- 用戶認證
- 分頁
- 自定義搜索組件
- Celery
- 搭建sentry平臺監控
- DRF學習
- drf概述
- Flask學習
- 項目拆分
- 三方模塊使用
- 爬蟲學習
- Http和Https區別
- 請求相關庫
- 解析相關庫
- 常見面試題
- 面試題
- 面試題解析
- 網絡原理
- 計算機網絡知識簡單介紹
- 詳解TCP三次握手、四次揮手及11種狀態
- 消息隊列和數據庫
- 消息隊列之RabbitMQ
- 數據庫之Redis
- 數據庫之初識MySQL
- 數據庫之MySQL進階
- 數據庫之MySQL補充
- 數據庫之Python操作MySQL
- Kafka常用命令
- Linux學習
- Linux基礎命令
- Git
- Git介紹
- Git基本配置及理論
- Git常用命令
- Docker
- Docker基本使用
- Docker常用命令
- Docker容器數據卷
- Dockerfile
- Docker網絡原理
- docker-compose
- Docker Swarm
- HTML
- CSS
- JS
- VUE