
最近python這么火,大家是不是也都在用啊。對于小編這種小白來說,對于python的一切都很迷茫,又很好奇,這不就看見了這樣一篇文章:當python 中混進一只薛定諤的貓……為此,小編還專門去問了度娘python和薛定諤的貓有啥關系。如果你也想知道的話,跟小編一起來看吧。
以下文章來源: Python貓
作者:豌豆花下貓
圖片來源:pexels
Python 是一門強大的動態(tài)語言,那動態(tài)體現在哪里,強大又體現在哪里呢?
除了好的方面,Python 的動態(tài)性是否還藏著一些使用陷阱呢,有沒有辦法識別與避免呢?
沿著它的動態(tài)特性話題,貓哥有幾篇文章依次探及了:動態(tài)修改變量、動態(tài)定義函數、動態(tài)執(zhí)行代碼等內容,然而,當混合了變量賦值、動態(tài)賦值、命名空間、作用域、函數的編譯原理等等內容時,問題就可能會變得非常棘手。
因此,這篇文章將前面一些內容融匯起來,再做一次延展的討論,希望能夠理清一些使用的細節(jié),更深入地探索 Python 語言的奧秘。
先看看這一個例子:
# 例0 def foo(): exec('y = 1 + 1') z = locals()['y'] print(z) foo() # 輸出:2
exec() 函數的代碼塊中定義了變量 y,這個值可以被隨后的 locals() 取到,在賦值后也打印了出來。然而,在這個例子的基礎上,只需做出小小的改變,結果就可能大不相同了。
# 例1 def foo(): exec('y = 1 + 1') y = locals()['y'] print(y) foo() # 報錯:KeyError: 'y'
把前例的 z 改為 y ,就報錯了。其中,KeyError 指的是在字典中不存在對應的 key 。為什么會這樣呢,新賦值的變量是 y 或者 z,為什么對結果有這么不同的影響?
試試把 exec 去掉,不報錯!
# 例2 def foo(): y = 1 + 1 y = locals()['y'] print(y) foo() # 2
問題:直接對 y 賦值,跟動態(tài)地在 exec() 中賦值,會對 locals() 取值產生怎樣的影響?
再試試對例 1 的 locals() 先賦值,還是報錯:
# 例3 def foo(): exec('y = 1 + 1') boc = locals() y = boc['y'] print(y) foo() # KeyError: 'y'
先做一次賦值,難道沒有用么?也不是,如果把賦值的順序調前,就不報錯了:
# 例4 def foo(): boc = locals() exec('y = 1 + 1') y = boc['y'] print(y) foo() # 2
也就是說,locals() 的值并不是固定的,它的值與調用時的上下文相關,調用 locals() 的時機至關重要。
然而,如果想要驗證一下,在函數中增加一個 locals() 的打印,這個動作卻會影響到最終的執(zhí)行結果。
# 例5 def foo(): boc = locals() exec('y = 1 + 1') print(locals()) y = boc['y'] print(y) foo() # {'boc': {...}} # KeyError: 'y'
這到底是怎么回事呢?
以上例子在細微之處有較大的不同,主要由于以下知識點的影響:
1、變量的聲明與賦值
2、locals() 取值與修改的邏輯
3、locals() 字典與局部命名空間的關系
4、函數的編譯,抽象語法樹的解析
注意:exec() 函數有兩個缺省的參數 globals() 與 locals() (與內置函數同名),起的是限定字符串參數中變量的作用,若添加出來,只會增加以上例子的復雜度,因此,我們都做缺省處理,這里討論的是 exec() 只有一個參數的情況。
在某些編程語言中,變量的聲明與賦值是可以分開的,例如在聲明時寫 int a ,需要賦值時,再寫 a = 1 ,當然也可不拆分,則是 int a = 1 。
對應到 Python 中,情況就不同了,這兩個動作在書寫時是合二為一的。首先它不用指定變量的類型,任何時候都不需要(也不能)在變量前加類型(如 int),其次,聲明與賦值過程無法拆分書寫,即只能寫成 a = 1 這樣??雌饋硭渌Z言的賦值寫法一樣,但實際上,它的效果是 int a = 1 。
這雖然是一種便利,但也隱藏了一個不易察覺的陷阱(劃重點):當看到 a = 1 時,你無法確定 a 是初次聲明的,還是已被聲明過的。
關于 locals() 的創(chuàng)建過程,在《Python 動態(tài)賦值的陷阱》文中有所分析,locals() 字典是局部命名空間的代理,它會采集局部作用域的變量,代碼運行期若動態(tài)修改局部變量,只會影響該字典,并不會影響真正的局部作用域的變量。因此,當再次調用 locals() 時,由于重新采集,則動態(tài)修改的內容會被丟棄。
運行期的局部命名空間不可改變,這意味著 exec() 函數中的變量賦值不會對它產生影響,但 locals() 字典是可變的,會受到 exec() 函數的影響。
關于函數的編譯,我在《Python與家國天下》中寫到了對抽象語法樹的分析,Python 在編譯時就確定了局部作用域內合法的變量名,在運行時再與內容綁定。作用域內變量的解析跟它的執(zhí)行順序無關,更與是否會被執(zhí)行無關。
以上內容是前提,友情提示,如你有理解模糊之處,請先閱讀對應的文章。接下來則是基于這些內容而作的分析。
我不敢保證每個細節(jié)都準確無誤,但這個分析力求達到深入淺出、面面俱到、邏輯自恰,而且順便幽默有趣……
例 0 中,局部作用域內雖然沒有 ‘y’,但 exec() 函數動態(tài)創(chuàng)建了它,因此動態(tài)地寫入了 locals() 字典中,所以能查找到而不報錯。
例 1 中,exec() 不影響局部作用域,即此時 y 未在局部作用域內做過聲明與賦值,接下來的一句才是第一次在局部作用域中對 y 作聲明與賦值 !
y = locals()['y'] ,等號左側在做聲明,只要等號右側的結果成立,整個聲明與賦值的過程就成立。右側需在 locals() 字典中查找 y 對應的值。
在創(chuàng)建 locals() 字典時,由于局部作用域內有變量 y 的聲明,因此我們首先在其中采集到了 y,而不必在 exec() 函數的動態(tài)結果中查找。這就有了字典的一個 key,接著要匹配這個 key 對應的值,也即 y 所綁定的值。
但是,剛才說了這是 y 的第一次賦值,并未完成呢,因此 y 并無有效的綁定值。
矛盾出現了,這里有點繞,我們理一下:左側的 y 等著完成賦值,因此需要右側的執(zhí)行結果;而右側的字典需要使用到 y 的值,因此就依賴著左側的 y 完成賦值。兩邊的操作都未完成,但雙方都需要依賴對方先完成,這是個無法破解的死局。
可以說,y 的值是一團混沌,它必然等于 “l(fā)ocals()['y']” ,然而只有解開這團代碼才能確切得到結果——只有打開籠子才知道結果,你是否想到了薛定諤的那只貓呢?
locals() 字典雖然拿到了 y 的名,卻拿不到它的實,空歡喜一場,所以報 KeyError。
例 3 同理,未完成賦值就使用,所以報錯。
例 2 中,y 在二次賦值的過程時,局部命名空間中已經存在著有效的 y 等于 2,因此 locals() 查找到它而用于賦值,所以不報錯。
至于例 4,它跟例 3 只差了一個執(zhí)行順序,為什么不會報錯呢?還有更奇怪的,在例 4 上再加一個打?。ɡ?),理應不會影響結果,可事實卻是又報錯了,為什么?
例 4 中,boc = locals() 這句同樣存在循環(huán)引用的問題,因此執(zhí)行后的字典中沒有 y,接著 exec() 這句動態(tài)地修改了 locals(),執(zhí)行后 boc 的結果是 {'y' : 2},因此再下一句的 boc['y'] 能查找到結果,而不報錯。
例 4 與例 3 的 ”y = boc['y']“ ,雖然都是第一次在局部作用域中聲明與賦值 y,但例 4 的 boc 已被 exec() 修改過,因此它能取到實實在在的值,就不再有循環(huán)引用的問題了。
接著看例 5,第一個 locals() 還是存在循環(huán)引用現象,接著 exec() 往字典中寫入變量 y,但是,第二個 locals() 又觸發(fā)了新的創(chuàng)建字典過程,會把 exec() 的執(zhí)行結果覆蓋,因此進入第二輪循環(huán)引用,導致報錯。
例 5 與例 4 的不同在于,它是根據局部作用域重新生成的字典,其效果等同于例 3。
另外,請?zhí)貏e注意打印的結果:{'boc': {…}} 。
這個結果說明,第二個 locals() 是一個字典,而且它只有唯一的 key 是 ’boc‘,而 ’boc‘ 映射的是第一個 locals() 字典,也即是 {…} 。這個寫法表示它內部出現了循環(huán)引用,直觀地證實了前面的所有分析。
字典內部出現循環(huán)引用 ,這個現象極其罕見!前面雖然做了分析,但看到這里的時候,不知道你是否覺得不可思議?
之所以第一次的循環(huán)引用能被記錄下來,原因在于我們沒有試圖去取出 ’y‘ 的值,而第二個循環(huán)引用則由于取值報錯而無法記錄下來。
這個例子告訴大家:薛定諤的貓混入了 Python 的字典中,而且答案是,打開籠子,這只貓就會死亡。
字典的循環(huán)引用現象在幾個例子中扮演了極其重要的角色,但是往往被人忽視。之所以難以被人覺察,原因還是前面劃重點的內容:當看到 a = 1 時,你無法確定 a 是初次聲明的,還是已被聲明過的。
在《Python與家國天下》文中,貓哥分析了兩類經典的報錯:name 'x' is not defined、local variable 'x' referenced before assignment。它們通常也是由于聲明與賦值不分,而導致的失察。
本文中的 KeyError 實際上就是“l(fā)ocal variable 'y' referenced before assignment”,y已defined 而未 assigned,導致 reference 時報錯。
已賦值還是未賦值,這是個問題。也是一只貓。
最后,盡管這只貓在暗中搗了大亂,我們還是要感謝它:感謝它串聯(lián)了其它知識被我們“一鍋端”,感謝它為這篇抽象燒腦的文章?lián)铣隽藥追只顫娚鷦拥娜の丁ㄒ约?,感謝它帶來的標題靈感,不知道有多少人是沖著標題而閱讀的?)
數據分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
SQL Server 中 CONVERT 函數的日期轉換:從基礎用法到實戰(zhàn)優(yōu)化 在 SQL Server 的數據處理中,日期格式轉換是高頻需求 —— 無論 ...
2025-09-18MySQL 大表拆分與關聯(lián)查詢效率:打破 “拆分必慢” 的認知誤區(qū) 在 MySQL 數據庫管理中,“大表” 始終是性能優(yōu)化繞不開的話題。 ...
2025-09-18CDA 數據分析師:表結構數據 “獲取 - 加工 - 使用” 全流程的賦能者 表結構數據(如數據庫表、Excel 表、CSV 文件)是企業(yè)數字 ...
2025-09-18DSGE 模型中的 Et:理性預期算子的內涵、作用與應用解析 動態(tài)隨機一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開始提取前,需先判斷 TIF 文件的類型 —— ...
2025-09-17CDA 數據分析師:解鎖表結構數據特征價值的專業(yè)核心 表結構數據(以 “行 - 列” 規(guī)范存儲的結構化數據,如數據庫表、Excel 表、 ...
2025-09-17Excel 導入數據含缺失值?詳解 dropna 函數的功能與實戰(zhàn)應用 在用 Python(如 pandas 庫)處理 Excel 數據時,“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗與 t 檢驗:差異、適用場景與實踐應用 在數據分析與統(tǒng)計學領域,假設檢驗是驗證研究假設、判斷數據差異是否 “ ...
2025-09-16CDA 數據分析師:掌控表格結構數據全功能周期的專業(yè)操盤手 表格結構數據(以 “行 - 列” 存儲的結構化數據,如 Excel 表、數據 ...
2025-09-16MySQL 執(zhí)行計劃中 rows 數量的準確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調優(yōu)中,EXPLAIN執(zhí)行計劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對象的 text 與 content:區(qū)別、場景與實踐指南 在 Python 進行 HTTP 網絡請求開發(fā)時(如使用requests ...
2025-09-15CDA 數據分析師:激活表格結構數據價值的核心操盤手 表格結構數據(如 Excel 表格、數據庫表)是企業(yè)最基礎、最核心的數據形態(tài) ...
2025-09-15Python HTTP 請求工具對比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請求(如接口調用、數據爬取 ...
2025-09-12解決 pd.read_csv 讀取長浮點數據的科學計數法問題 為幫助 Python 數據從業(yè)者解決pd.read_csv讀取長浮點數據時的科學計數法問題 ...
2025-09-12CDA 數據分析師:業(yè)務數據分析步驟的落地者與價值優(yōu)化者 業(yè)務數據分析是企業(yè)解決日常運營問題、提升執(zhí)行效率的核心手段,其價值 ...
2025-09-12用 SQL 驗證業(yè)務邏輯:從規(guī)則拆解到數據把關的實戰(zhàn)指南 在業(yè)務系統(tǒng)落地過程中,“業(yè)務邏輯” 是連接 “需求設計” 與 “用戶體驗 ...
2025-09-11塔吉特百貨孕婦營銷案例:數據驅動下的精準零售革命與啟示 在零售行業(yè) “流量紅利見頂” 的當下,精準營銷成為企業(yè)突圍的核心方 ...
2025-09-11CDA 數據分析師與戰(zhàn)略 / 業(yè)務數據分析:概念辨析與協(xié)同價值 在數據驅動決策的體系中,“戰(zhàn)略數據分析”“業(yè)務數據分析” 是企業(yè) ...
2025-09-11Excel 數據聚類分析:從操作實踐到業(yè)務價值挖掘 在數據分析場景中,聚類分析作為 “無監(jiān)督分組” 的核心工具,能從雜亂數據中挖 ...
2025-09-10統(tǒng)計模型的核心目的:從數據解讀到決策支撐的價值導向 統(tǒng)計模型作為數據分析的核心工具,并非簡單的 “公式堆砌”,而是圍繞特定 ...
2025-09-10