<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python 世界的黑客帝國

          共 5104字,需瀏覽 11分鐘

           ·

          2020-10-30 02:06

          文 |?太陽雪

          來源:Python 技術(shù)「ID: pythonall」

          相比于子彈時間和火爆場景,我更喜歡《黑客帝國》故事背景的假設(shè) —— 人們熟悉的世界是虛構(gòu)的,是機(jī)器給人大腦輸入的幻象,而幻象是不完美的,存在一些不符合自然規(guī)律的地方,這些地方或多或少的展示了幻象世界的破綻和真實(shí)世界的樣子,如果你看過《黑客帝國》動畫版《超越極限》和《世界紀(jì)錄》,將會有更深刻的感受

          我們熟悉的、賴以生存的 Python 世界,其實(shí)也是個虛擬的,這個虛擬世界展示給我們無比絢爛的場景和功能的同時,也存在一些超乎常理和認(rèn)知的地方,今天就帶你一起探尋一些那些超自然點(diǎn),以及它背后的真實(shí)世界

          神奇的海象操作符

          海象操作符 := 是 Python3.8 引入的一個新特性,意為節(jié)省一次臨時變量的賦值操作,例如:

          a?=?[1,2,3,4,5]
          n?=?len(a)
          if?n?>?4:
          ????print(n)

          意思是,如果列表 a 的長度大于 4,則打印 a 的長度,為了避免對列表長度的兩次求解,利用變量 n 存儲 a 的長度,合情合理

          如果用 海象操作符(:=),會是這樣:

          a?=?[1,2,3,4,5]
          if?n?:=?len(n)?>?4:
          ????print(n)

          可以看到,省去了臨時變量 n 的定義,通過海象操作符,一舉兩得

          不得不說,Python 為能讓我們提高效率,真是挖空心思,剛剛發(fā)布的 正式版 Python3.9,也是為提升效率做了多處改善

          海象的表演

          不過,看下面的代碼

          >>>?a?=?"wtf_walrus"
          >>>?a
          'wtf_walrus'

          >>>?a?:=?"wtf_walrus"??#?報錯!
          ??File?"",?line?1
          ????a?:=?'wtf_walrus'
          ??????^
          SyntaxError:?invalid?syntax

          >>>?(a?:=?"wtf_walrus")?#?奇跡發(fā)生,竟然通過了!
          'wtf_walrus'
          >>>?a
          'wtf_walrus'

          再來一段

          >>>?a?=?6,?9??#?元組賦值
          >>>?a??#?結(jié)果正常
          (6,?9)

          >>>?(a?:=?6,?9)??#?海象賦值,表達(dá)式結(jié)果正常
          (6,?9)
          >>>?a??#?臨時變量竟然不同
          6

          >>>?a,?b?=?6,?9?#?解包賦值
          >>>?a,?b
          (6,?9)
          >>>?(a,?b?=?16,?19)?# Oh no!
          ??File?"",?line?1
          ????(a,?b?=?6,?9)
          ??????????^
          SyntaxError:?invalid?syntax

          >>>?(a,?b?:=?16,?19)?#?這里竟然打印出三員元組!
          (6,?16,?19)

          >>>?a?#?問題是?a?竟然沒變
          6

          >>>?b
          16

          解密海象

          • 非括號表達(dá)式的海象賦值操作海象操作符(:=)適用于一個表達(dá)式內(nèi)部的作用域,沒有括號,相當(dāng)于全局作業(yè)域,是會受到編譯器限制的

          • 括號里的賦值操作相應(yīng)的,賦值操作符(=)不能放在括號里,因?yàn)樗枰谌肿饔糜蛑袌?zhí)行,而非在一個表達(dá)式內(nèi)

          • 海象操作符的本質(zhì)海象操作符的語法形式為 Name := expr,Name 為正常的標(biāo)識符,expr 為正常的表達(dá)式,因此可迭代的 裝包解包 表現(xiàn)的結(jié)果會和期望不同

            • (a := 6, 9) 實(shí)際上會被解析為 ((a := 6), 9),最終,a 的值為 6,驗(yàn)證一下:

              >>>?(a?:=?6,?9)?==?((a?:=?6),?9)
              True
              >>>?x?=?(a?:=?696,?9)
              >>>?x
              (696,?9)
              >>>?x[0]?is?a?#?引用了相同的值
              True
            • 同樣的,(a, b := 16, 19) 相當(dāng)于 (a, (b := 16), 19,原來如此

          不安分的字符串

          先建立一個感知認(rèn)識

          >>>?a?=?"some_string"
          >>>?id(a)
          140420665652016
          >>>?id("some"?+?"_"?+?"string")?#?不同方式創(chuàng)建的字符串實(shí)質(zhì)是一樣的.
          140420665652016

          奇特的事情即將發(fā)生

          >>>?a?=?"wtf"
          >>>?b?=?"wtf"
          >>>?a?is?b
          True

          >>>?a?=?"wtf!"
          >>>?b?=?"wtf!"
          >>>?a?is?b??#?什么鬼!
          False

          如果將這段代碼寫入腳本文件,用 Python 運(yùn)行,結(jié)果卻是對的:

          a?=?"wtf!"
          b?=?"wtf!"
          print(a?is?b)??#?將打印出?True

          還有更神奇的,在 Python3.7 之前的版本中,會有下面的現(xiàn)象:

          >>>?'a'?*?20?is?'aaaaaaaaaaaaaaaaaaaa'
          True
          >>>?'a'?*?21?is?'aaaaaaaaaaaaaaaaaaaaa'
          False

          20 個字符 a 組合起來等于 20個 a 的字符串,而 21 個就不相等

          揭秘字符串

          計(jì)算機(jī)世界里,任何奇特的現(xiàn)象都有其必然原因

          • 這些字符串行為,是由于 Cpython 在編譯優(yōu)化時,某些情況下會對不可變的對象做存儲,新建時之間建立引用,而不是創(chuàng)建新的,這種技術(shù)被稱作 字符串駐留(string interning),這樣做可以節(jié)省內(nèi)存和提高效率

          • 上面代碼中,字符串被隱式駐留了,什么情況下才會被駐留呢?

            • 所有長度為 0 和 1 的字符串都會被駐留
            • 字符串在編譯時被駐留,運(yùn)算中不會("wtf" 會駐留,"".join("w", "t", "f") 則不會)
            • 只有包含了字母、數(shù)值和下劃線的字符串才會被駐留,這就是為什么 "wtf!" 不會被駐留的原因(其中還有字符 !
          • 如果 a 和 b 的賦值 "wtf!" 語句在同一行,Python 解釋器會創(chuàng)建一個對象,然后讓兩個變量指向這個對象。如果在不同行,解釋器就不知道已經(jīng)有了 "wtf!" 對象,所以會創(chuàng)建新的(原因是 "wtf!" 不會被駐留)

          • 像 IPython 這樣的交互環(huán)境中,語句是單行執(zhí)行的,而腳本文件中,代碼是被同時編譯的,具有相同的編譯環(huán)境,所以就能理解,代碼文件中不同行的不被駐留字符串引用同一個對象的現(xiàn)象

          • 常量折疊(constant folding)是 Python 的一個優(yōu)化技術(shù):窺孔優(yōu)化(peephole optimization),如 a = "a"*20,這樣的語句會在編譯時展開,以減少運(yùn)行時的運(yùn)行消耗,為了不至于讓 pyc 文件過大,將展開字符限制在 20 個以內(nèi),不然想想這個 "a"*10**100 語句將產(chǎn)生多大的 pyc 文件

          • 注意 Python3.7 以后的版本中 常量折疊 的問題得到了改善,不過還不清楚具體原因(矩陣變的更復(fù)雜了)

          小心鏈?zhǔn)讲僮?/h2>

          來一段騷操作

          >>>?(False?==?False)?in?[False]?#?合乎常理
          False
          >>>?False?==?(False?in?[False])?#?也沒問題
          False
          >>>?False?==?False?in?[False]?#?現(xiàn)在感覺如何?
          True

          >>>?True?is?False?==?False
          False
          >>>?False?is?False?is?False
          True

          >>>?1?>?0?1
          True
          >>>?(1?>?0)?1
          False
          >>>?1?>?(0?1)
          False

          不知到你看到這段代碼的感受,反正我看第一次到時,懷疑我學(xué)的 Python 是假冒的~

          到底發(fā)生了什么

          按照 Python 官方文檔,表達(dá)式章節(jié),值比較小節(jié)的描述(https://docs.python.org/2/reference/expressions.html#not-in):

          通常情況下,如果 a、b、c、...、y、z 是表達(dá)式,op1、op2、...、opN 是比較運(yùn)算符,那么 a op1 b op2 c ... y opN z 等價于 a op1 b and b op2 c and ... y opN z,除了每個表達(dá)式只被計(jì)算一次的特性

          基于以上認(rèn)知,我們重新審視一下上面看起來讓人迷惑的語句:

          • False is False is False 等價于 (False is False) and (False is False)
          • True is False == False 等價于 True is False and False == False,現(xiàn)在可以看出,第一部分 True is False 的求值為 False, 所以整個表達(dá)式的值為 False
          • 1 > 0 < 1 等價于 1 > 0 and 0 < 1,所以表達(dá)式求值為 True
          • 表達(dá)式 (1 > 0) < 1 等價于 True < 1,另外 int(True) 的值為 1,且 True + 1 的值為 2,那么 1 < 1 就是 False

          到底 is(是) 也不 is(是)

          我直接被下面的代碼驚到了

          >>>?a?=?256
          >>>?b?=?256
          >>>?a?is?b
          True

          >>>?a?=?257
          >>>?b?=?257
          >>>?a?is?b
          False

          再來一個

          >>>?a?=?[]
          >>>?b?=?[]
          >>>?a?is?b
          False

          >>>?a?=?tuple()
          >>>?b?=?tuple()
          >>>?a?is?b
          True

          同樣是數(shù)字,同樣是對象,待遇咋就不一樣尼……

          我們逐一理解下

          is 和 == 的區(qū)別

          • is 操作符用于檢查兩個變量引用的是否同一個對象實(shí)例

          • == 操作符用于檢查兩個變量引用對象的值是否相等

          • 所以 is 用于引用比較,== 用于值比較,下面的代碼能更清楚的說明這一點(diǎn):

            >>>?class?A:?pass
            >>>?A()?is?A()??#?由于兩個對象實(shí)例在不同的內(nèi)存空間里,所以表達(dá)式值為?False
            False

          256 是既存對象,而 257 不是

          這個小標(biāo)題讓人很無語,還確實(shí)對象和對象不一樣

          在 Python 里 從 -5 到 256 范圍的數(shù)值,是預(yù)先初始化好的,如果值為這個范圍內(nèi)的數(shù)字,會直接建立引用,否則就會創(chuàng)建

          這就解釋了為啥 同樣的 257,內(nèi)存對象不同的現(xiàn)象了

          為什么要這樣做?官方的解釋為,這個范圍的數(shù)值比較常用(https://docs.python.org/3/c-api/long.html)

          不可變的空元組

          和 -5 到 256 數(shù)值預(yù)先創(chuàng)建一樣,對于不可變的對象,Python 解釋器也做了預(yù)先創(chuàng)建,例如 對空的 Tuple 對象

          這就能解釋為什么 空列表對象之間引用不同,而空元組之間的引用確實(shí)相同的現(xiàn)象了

          被吞噬的 Javascript

          先看看過程

          some_dict?=?{}
          some_dict[5.5]?=?"Ruby"
          some_dict[5.0]?=?"JavaScript"
          some_dict[5]?=?"Python"

          print(some_dict[5.5])??#?Ruby
          print(some_dict[5.0])??# Python Javascript 去哪了?

          背后的原因

          • Python 字典對象的索引,是通過鍵值是否相等和比較鍵的哈希值來進(jìn)行查找的

          • 具有相同值的不可變對象的哈希值是相同的

            >>>?5?==?5.0
            True
            >>>?hash(5)?==?hash(5.0)
            True
          • 于是我們就能理解,當(dāng)執(zhí)行 some_dict[5] = "Python" 時,會覆蓋掉前面定義的 some_dict[5.0] = "Javascript",因?yàn)?5 和 5.0 具有相同的哈希值

          需要注意的是:有可能不同的值具有相同的哈希值,這種現(xiàn)象被稱作 哈希沖突

          關(guān)于字典對象使用哈希值作為索引運(yùn)算的更深層次的原因,有興趣的同學(xué)可以參考 StackOverflow 上的回答,解釋的很精彩,網(wǎng)址是:https://stackoverflow.com/questions/32209155/why-can-a-floating-point-dictionary-key-overwrite-an-integer-key-with-the-same-v/32211042

          總結(jié)

          限于篇幅(精力)原因,今天就介紹這幾個 Python 宇宙中的異?,F(xiàn)象,更多的異常現(xiàn)象,收錄在 satwikkansal 的 wtfpython 中(https://github.com/satwikkansal/wtfpython)。

          任何華麗美好的背后都是各種智慧、技巧、妥協(xié)、辛苦的支撐,不是有那么一句話嘛:如果你覺得輕松自如,比如有人在負(fù)重前行。而這個人就是我們喜愛的 Python 及其 編譯器,要更好的理解一個東西,需要了解它背后的概念原理和理念,期望通過這篇短文對你有所啟發(fā)

          本文代碼測試版本為 Python3.8.2,實(shí)踐中遇到問題,可留言或在交流群討論

          參考

          • https://github.com/satwikkansal/wtfpython
          • https://docs.python.org/2/reference/expressions.html#not-in
          • https://docs.python.org/3/c-api/long.html
          • https://github.com/leisurelicht/wtfpython-cn

          PS公號內(nèi)回復(fù)「Python」即可進(jìn)入Python 新手學(xué)習(xí)交流群,一起 100 天計(jì)劃!


          老規(guī)矩,兄弟們還記得么,右下角的 “在看” 點(diǎn)一下,如果感覺文章內(nèi)容不錯的話,記得分享朋友圈讓更多的人知道!

          代碼獲取方式

          識別文末二維碼,回復(fù):201027

          瀏覽 35
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  内射网站在线看 | 被操逼视频 | 日本黄色色情视频 | 黄色影片A片 | 中文一级毛片 |