成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

當Python中混進一只薛定諤的貓……

wua_wua2012 / 2520人閱讀

摘要:薛定諤的貓以上內(nèi)容是前提,友情提示,如你有理解模糊之處,請先閱讀對應(yīng)的文章。這個例子告訴大家薛定諤的貓混入了的字典中,而且答案是,打開籠子,這只貓就會死亡。

本文原創(chuàng)并首發(fā)于公眾號【Python貓】,未經(jīng)授權(quán),請勿轉(zhuǎn)載。
原文地址:https://mp.weixin.qq.com/s/-f...

Python 是一門強大的動態(tài)語言,那動態(tài)體現(xiàn)在哪里,強大又體現(xiàn)在哪里呢?除了好的方面,Python 的動態(tài)性是否還藏著一些使用陷阱呢,有沒有辦法識別與避免呢?

沿著它的動態(tài)特性話題,貓哥有幾篇文章依次探及了:動態(tài)修改變量、動態(tài)定義函數(shù)、動態(tài)執(zhí)行代碼等內(nèi)容,然而,當混合了變量賦值、動態(tài)賦值、命名空間、作用域、函數(shù)的編譯原理等等內(nèi)容時,問題就可能會變得非常棘手。

因此,這篇文章將前面一些內(nèi)容融匯起來,再做一次延展的討論,希望能夠理清一些使用的細節(jié),更深入地探索 Python 語言的奧秘。

(1)疑惑重重的例子

先看看這一個例子:

# 例0
def foo():
    exec("y = 1 + 1")
    z = locals()["y"]
    print(z)
    
foo()

# 輸出:2

exec() 函數(shù)的代碼塊中定義了變量 y,這個值可以被隨后的 locals() 取到,在賦值后也打印了出來。然而,在這個例子的基礎(chǔ)上,只需做出小小的改變,結(jié)果就可能大不相同了。

# 例1
def foo():
    exec("y = 1 + 1")
    y = locals()["y"]
    print(y)
    
foo()

# 報錯:KeyError: "y"

把前例的 z 改為 y ,就報錯了。其中,KeyError 指的是在字典中不存在對應(yīng)的 key 。為什么會這樣呢,新賦值的變量是 y 或者 z,為什么對結(jié)果有這么不同的影響?

試試把 exec 去掉,不報錯!

# 例2
def foo():
    y = 1 + 1
    y = locals()["y"]
    print(y)

foo()

# 2

問題:直接對 y 賦值,跟動態(tài)地在 exec() 中賦值,會對 locals() 取值產(chǎn)生怎樣的影響?

再試試對例 1 的 locals() 先賦值,還是報錯:

# 例3
def foo():
    exec("y = 1 + 1")
    boc = locals()
    y = boc["y"]
    print(y)
 
foo()

# KeyError: "y"

先做一次賦值,難道沒有用么?也不是,如果把賦值的順序調(diào)前,就不報錯了:

# 例4
def foo():
    boc = locals()
    exec("y = 1 + 1")
    y = boc["y"]
    print(y)

foo()

# 2

也就是說,locals() 的值并不是固定的,它的值與調(diào)用時的上下文相關(guān),調(diào)用 locals() 的時機至關(guān)重要。

然而,如果想要驗證一下,在函數(shù)中增加一個 locals() 的打印,這個動作卻會影響到最終的執(zhí)行結(jié)果。

# 例5
def foo():
    boc = locals()
    exec("y = 1 + 1")
    print(locals())
    y = boc["y"]
    print(y)

foo()

# {"boc": {...}}
# KeyError: "y"

這到底是怎么回事呢?

(2)多元知識的儲備

以上例子在細微之處有較大的不同,主要由于以下知識點的影響:

1、變量的聲明與賦值

2、locals() 取值與修改的邏輯

3、locals() 字典與局部命名空間的關(guān)系

4、函數(shù)的編譯,抽象語法樹的解析

注意:exec() 函數(shù)有兩個缺省的參數(shù) globals() 與 locals() (與內(nèi)置函數(shù)同名),起的是限定字符串參數(shù)中變量的作用,若添加出來,只會增加以上例子的復(fù)雜度,因此,我們都做缺省處理,這里討論的是 exec() 只有一個參數(shù)的情況。

在某些編程語言中,變量的聲明與賦值是可以分開的,例如在聲明時寫 int a ,需要賦值時,再寫 a = 1 ,當然也可不拆分,則是 int a = 1 。

對應(yīng)到 Python 中,情況就不同了,這兩個動作在書寫時是合二為一的。首先它不用指定變量的類型,任何時候都不需要(也不能)在變量前加類型(如 int),其次,聲明與賦值過程無法拆分書寫,即只能寫成 a = 1 這樣。看起來它跟其它語言的賦值寫法一樣,但實際上,它的效果是 int a = 1

這雖然是一種便利,但也隱藏了一個不易察覺的陷阱(劃重點):當看到 a = 1 時,你無法確定 a 是初次聲明的,還是已被聲明過的。

關(guān)于 locals() 的創(chuàng)建過程,在《Python 動態(tài)賦值的陷阱》文中有所分析,locals() 字典是局部命名空間的代理,它會采集局部作用域的變量,代碼運行期若動態(tài)修改局部變量,只會影響該字典,并不會影響真正的局部作用域的變量。因此,當再次調(diào)用 locals() 時,由于重新采集,則動態(tài)修改的內(nèi)容會被丟棄。

運行期的局部命名空間不可改變,這意味著 exec() 函數(shù)中的變量賦值不會對它產(chǎn)生影響,但 locals() 字典是可變的,會受到 exec() 函數(shù)的影響。

而關(guān)于函數(shù)的編譯,我在《Python與家國天下》中寫到了對 抽象語法樹 的分析,Python 在編譯時就確定了局部作用域內(nèi)合法的變量名,在運行時再與內(nèi)容綁定。作用域內(nèi)變量的解析跟它的執(zhí)行順序無關(guān),更與是否會被執(zhí)行無關(guān)。

(3)薛定諤的貓

以上內(nèi)容是前提,友情提示,如你有理解模糊之處,請先閱讀對應(yīng)的文章。接下來則是基于這些內(nèi)容而作的分析。

我不敢保證每個細節(jié)都準確無誤,但這個分析力求達到深入淺出、面面俱到、邏輯自恰,而且順便幽默有趣……

例 0 中,局部作用域內(nèi)雖然沒有 ‘y’,但 exec() 函數(shù)動態(tài)創(chuàng)建了它,因此動態(tài)地寫入了 locals() 字典中,所以能查找到而不報錯。

例 1 中,exec() 不影響局部作用域,即此時 y 未在局部作用域內(nèi)做過聲明與賦值,接下來的一句才是第一次在局部作用域中對 y 作聲明與賦值 !

y = locals()["y"] ,等號左側(cè)在做聲明,只要等號右側(cè)的結(jié)果成立,整個聲明與賦值的過程就成立。右側(cè)需在 locals() 字典中查找 y 對應(yīng)的值。

在創(chuàng)建 locals() 字典時,由于局部作用域內(nèi)有變量 y 的聲明,因此我們首先在其中采集到了 y,而不必在 exec() 函數(shù)的動態(tài)結(jié)果中查找。這就有了字典的一個 key,接著要匹配這個 key 對應(yīng)的值,也即 y 所綁定的值。

但是,剛才說了這是 y 的第一次賦值,并未完成呢,因此 y 并無有效的綁定值。

矛盾出現(xiàn)了,這里有點繞,我們理一下:左側(cè)的 y 等著完成賦值,因此需要右側(cè)的執(zhí)行結(jié)果;而右側(cè)的字典需要使用到 y 的值,因此就依賴著左側(cè)的 y 完成賦值。兩邊的操作都未完成,但雙方都需要依賴對方先完成,這是個無法破解的死局。

可以說,y 的值是一團混沌,它必然等于 “l(fā)ocals()["y"]” ,然而只有解開這團代碼才能確切得到結(jié)果——只有打開籠子才知道結(jié)果,你是否想到了薛定諤的那只貓呢?

locals() 字典雖然拿到了 y 的名,卻拿不到它的實,空歡喜一場,所以報 KeyError。

例 3 同理,未完成賦值就使用,所以報錯。

例 2 中,y 在二次賦值的過程時,局部命名空間中已經(jīng)存在著有效的 y 等于 2,因此 locals() 查找到它而用于賦值,所以不報錯。

至于例 4,它跟例 3 只差了一個執(zhí)行順序,為什么不會報錯呢?還有更奇怪的,在例 4 上再加一個打?。ɡ?),理應(yīng)不會影響結(jié)果,可事實卻是又報錯了,為什么?

例 4 中,boc = locals() 這句同樣存在循環(huán)引用的問題,因此執(zhí)行后的字典中沒有 y,接著 exec() 這句動態(tài)地修改了 locals(),執(zhí)行后 boc 的結(jié)果是 {"y" : 2},因此再下一句的 boc["y"] 能查找到結(jié)果,而不報錯。

例 4 與例 3 的 ”y = boc["y"]“ ,雖然都是第一次在局部作用域中聲明與賦值 y,但例 4 的 boc 已被 exec() 修改過,因此它能取到實實在在的值,就不再有循環(huán)引用的問題了。

接著看例 5,第一個 locals() 還是存在循環(huán)引用現(xiàn)象,接著 exec() 往字典中寫入變量 y,但是,第二個 locals() 又觸發(fā)了新的創(chuàng)建字典過程,會把 exec() 的執(zhí)行結(jié)果覆蓋,因此進入第二輪循環(huán)引用,導(dǎo)致報錯。

例 5 與例 4 的不同在于,它是根據(jù)局部作用域重新生成的字典,其效果等同于例 3。

另外,請?zhí)貏e注意打印的結(jié)果:{"boc": {…}} 。

這個結(jié)果說明,第二個 locals() 是一個字典,而且它只有唯一的 key 是 ’boc‘,而 ’boc‘ 映射的是第一個 locals() 字典,也即是 {...} 。這個寫法表示它內(nèi)部出現(xiàn)了循環(huán)引用,直觀地證實了前面的所有分析。

字典內(nèi)部出現(xiàn)循環(huán)引用 ,這個現(xiàn)象極其罕見!前面雖然做了分析,但看到這里的時候,不知道你是否覺得不可思議?

之所以第一次的循環(huán)引用能被記錄下來,原因在于我們沒有試圖去取出 ’y‘ 的值,而第二個循環(huán)引用則由于取值報錯而無法記錄下來。

這個例子告訴大家:薛定諤的貓混入了 Python 的字典中,而且答案是,打開籠子,這只貓就會死亡。

字典的循環(huán)引用現(xiàn)象在幾個例子中扮演了極其重要的角色,但是往往被人忽視。之所以難以被人覺察,原因還是前面劃重點的內(nèi)容:當看到 a = 1 時,你無法確定 a 是初次聲明的,還是已被聲明過的。

在《Python與家國天下》文中,貓哥分析了兩類經(jīng)典的報錯:name "x" is not defined、local variable "x" referenced before assignment。它們通常也是由于聲明與賦值不分,而導(dǎo)致的失察。

本文中的 KeyError 實際上就是 “l(fā)ocal variable "y" referenced before assignment”,y 已 defined 而未 assigned,導(dǎo)致 reference 時報錯。

已賦值還是未賦值,這是個問題。也是一只貓。

最后,盡管這只貓在暗中搗了大亂,我們還是要感謝它:感謝它串聯(lián)了其它知識被我們“一鍋端”,感謝它為這篇抽象燒腦的文章?lián)铣隽藥追只顫娚鷦拥娜の丁ㄒ约?,感謝它帶來的標題靈感,不知道有多少人是沖著標題而閱讀的?)

后記

本文中的幾個例子早在 3 月 24 日就想到了,但我沒法給自己一套完全滿意的解答。在與群內(nèi)小伙伴們陸續(xù)討論了一整個下午后,我依然不滿足,最終打消了寫入《深度辨析 Python 的 eval() 與 exec()》這篇文章的念頭。兩個月來,群內(nèi)偶爾討論過幾次相關(guān)的知識點,感謝好幾位同學(xué)(特別@櫻雨樓)的討論,我終于覺得時機到了(其實是稿荒啦),把沉睡近兩個月的草稿翻出來……如今的分析,我自認為是能說得通,而且關(guān)鍵細節(jié)無遺漏的,但仍可能有瑕疵,如果你有什么想交流的,歡迎給我留言。

公眾號【Python貓】, 本號連載優(yōu)質(zhì)的系列文章,有喵星哲學(xué)貓系列、Python進階系列、好書推薦系列、技術(shù)寫作、優(yōu)質(zhì)英文推薦與翻譯等等,歡迎關(guān)注哦。后臺回復(fù)“愛學(xué)習(xí)”,免費獲得一份學(xué)習(xí)大禮包。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/43813.html

相關(guān)文章

  • 諤的滾與深度學(xué)習(xí)中的物理

    摘要:如果將小磁針看作神經(jīng)元,磁針狀態(tài)看作激發(fā)與抑制,也可以用來構(gòu)建深度學(xué)習(xí)的模型,或者玻爾茲曼機。這么多的基礎(chǔ)理論,展現(xiàn)了深度學(xué)習(xí)中的無處不在的物理本質(zhì)。 最近朋友圈里有大神分享薛定諤的滾,一下子火了,當一個妹子叫你滾的時候,你永遠不知道她是在叫你滾還是叫你過來抱緊,這確實是一種十分糾結(jié)的狀態(tài),而薛定諤是搞不清楚的,他連自己的貓是怎么回事還沒有弄清楚。雖然人們對于薛定諤頭腦中那只被放射性物質(zhì)殘害...

    gnehc 評論0 收藏0
  • 2013年 Linux 領(lǐng)域以及開源界的重要事件盤點

    摘要:發(fā)布年月日,代號名稱為的發(fā)布正式版本。目前,最新的系列版本為年月日發(fā)布的版本,主要是對的安全更新。發(fā)布于年月初發(fā)布,代號薛定諤的貓,搭載和。 文章來源:程序員的資料庫 Linux 和開源軟件在過去的一年里面都取得了不小的進步。在這個特殊日子里面,我們對 2013 年這一年業(yè)界發(fā)生的重要事情分成了三個方面:Linux 發(fā)行版、重要周年慶?;顒印⒃饦I(yè)界較大關(guān)注的事件這三個方面進行了梳理...

    HelKyle 評論0 收藏0
  • 制作QQ微信支付寶三合一收款碼

    摘要:所以,我也想加一個打賞功能分析但在逛了一圈之后發(fā)現(xiàn),打賞插件基本上千篇一律的掃碼微信掃碼支付寶掃碼。但由于與微信無法直接喚醒,所以直接輸出一個與微信的二維碼,然后長按掃碼實現(xiàn)支付。 前言 最近在逛博客時,發(fā)現(xiàn)很多博客都帶了打賞功能,雖說打賞的人可能很少,但始終是一份心意,能讓博主知道自己寫的文章有用,能夠幫助到人。所以,我也想加一個打賞功能~ 分析 但在github逛了一圈之后發(fā)現(xiàn),打...

    Pines_Cheng 評論0 收藏0

發(fā)表評論

0條評論

wua_wua2012

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<