日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Python 的 51 個秘密曝光,Github 獲 2 萬星

 leafcho 2019-06-24

Python 的 51 個秘密曝光,Github 獲 2 萬星

文 | 網(wǎng)絡(luò) 編輯 | Earlgrey

推薦 | 編程派公眾號(ID:codingpy)

Python,是一個設(shè)計優(yōu)美的解釋型高級語言,它提供了很多能讓程序員感到舒適的功能特性。

但有的時候,Python一些特性導(dǎo)致的輸出結(jié)果,對于初學(xué)者就很難理解了。

一個解析51項堪稱是'秘密'的Python特性項目,在GitHub上徹底火了。

英文原版已經(jīng)拿到了近15000星,中文翻譯版也獲得了7500+星。

Python 的 51 個秘密曝光,Github 獲 2 萬星

Python 的 51 個秘密曝光,Github 獲 2 萬星

項目中的部分內(nèi)容,也許你聽說過,但依然可能會透露一些你所不知道的Python有趣特性。

我覺得這是學(xué)習(xí)編程語言內(nèi)部原理的好機會,而且我相信你也會從中獲得樂趣!

如果你是一位經(jīng)驗比較豐富的Python程序員,你可以試試能否一次就找到正確答案。

也許你對其中的一些例子比較熟悉,那這些案例能喚起你當(dāng)年踩坑時的甜蜜回憶。

這個項目的中文版全文大約2萬字,干貨多的快要溢出來了,大家可以先看一下目錄。

Python 的 51 個秘密曝光,Github 獲 2 萬星

示例結(jié)構(gòu)

所有示例的結(jié)構(gòu)都如下所示:

  1. > 一個精選的標(biāo)題


  2. # 準(zhǔn)備代碼.


  3. # 釋放魔法...


  4. Output (Python version):


  5. >>> 觸發(fā)語句


  6. 出乎意料的輸出結(jié)果


  7. (可選): 對意外輸出結(jié)果的簡短描述。


  8. 說明:


  9. 簡要說明發(fā)生了什么以及為什么會發(fā)生。


  10. 如有必要,舉例說明


  11. Output:


  12. >>>觸發(fā)語句#一些讓魔法變得容易理解的例子


  13. #一些正常的輸入


  14. 注意:所有的示例都在Python3.5.2版本的交互解釋器上測試過,如果不特別說明應(yīng)該適用于所有Python版本。


  15. 用法

我個人建議,最好依次閱讀下面的示例,并仔細閱讀設(shè)置例子最開始的代碼。

閱讀輸出結(jié)果

示例

微妙的字符串

1.

  1. >>> a = 'some_string'

  2. >>> id(a)

  3. 140420665652016

  4. >>> id('some' + '_' + 'string') # 注意兩個的id值是相同的.

  5. 140420665652016

2.

  1. >>> a = 'wtf'

  2. >>> b = 'wtf'

  3. >>> a is b

  4. True


  5. >>> a = 'wtf!'

  6. >>> b = 'wtf!'

  7. >>> a is b

  8. False


  9. >>> a, b = 'wtf!', 'wtf!'

  10. >>> a is b

  11. True

3.

  1. >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'

  2. True

  3. >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'

  4. False

說明:

這些行為是由于 Cpython 在編譯優(yōu)化時,某些情況下會嘗試使用已經(jīng)存在的不可變對象,而不是每次都創(chuàng)建一個新對象。(這種行為被稱作字符串的駐留[string interning])

發(fā)生駐留之后,許多變量可能指向內(nèi)存中的相同字符串對象。(從而節(jié)省內(nèi)存)

在上面的代碼中,字符串是隱式駐留的。何時發(fā)生隱式駐留則取決于具體的實現(xiàn)。這里有一些方法可以用來猜測字符串是否會被駐留:

所有長度為 0 和長度為 1 的字符串都被駐留。

字符串在編譯時被實現(xiàn)。('wtf' 將被駐留, 但是 ''.join(['w', 't', 'f'] 將不會被駐留)

字符串中只包含字母,數(shù)字或下劃線時將會駐留。所以 'wtf!' 由于包含!而未被駐留??梢栽谶@里找CPython對此規(guī)則的實現(xiàn)。

Python 的 51 個秘密曝光,Github 獲 2 萬星

當(dāng)在同一行將 a 和 b 的值設(shè)置為 'wtf!' 的時候, Python 解釋器會創(chuàng)建一個新對象, 然后同時引用第二個變量。

如果你在不同的行上進行賦值操作, 它就不會'知道'已經(jīng)有一個 wtf!對象 (因為 'wtf!' 不是按照上面提到的方式被隱式駐留的)。

它是一種編譯器優(yōu)化,特別適用于交互式環(huán)境。

常量折疊(constant folding) 是 Python 中的一種窺孔優(yōu)化(peephole optimization) 技術(shù)。

這意味著在編譯時表達式 'a'*20 會被替換為 'aaaaaaaaaaaaaaaaaaaa' 以減少運行時的時鐘周期。

只有長度小于 20 的字符串才會發(fā)生常量折疊。(為啥? 想象一下由于表達式'a'10*10 而生成的 .pyc 文件的大小)相關(guān)的源碼:

https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288

是時候來點蛋糕了!

1.

  1. some_dict = {}

  2. some_dict[5.5] = 'Ruby'

  3. some_dict[5.0] = 'JavaScript'

  4. some_dict[5] = 'Python'


  5. Output:


  6. >>> some_dict[5.5]

  7. 'Ruby'

  8. >>> some_dict[5.0]

  9. 'Python'

  10. >>> some_dict[5]

  11. 'Python'


  12. 'Python' 消除了 'JavaScript' 的存在?

說明:

Python 字典通過檢查鍵值是否相等和比較哈希值來確定兩個鍵是否相同。

具有相同值的不可變對象在Python中始終具有相同的哈希值。

  1. >>> 5 == 5.0

  2. True

  3. >>> hash(5) == hash(5.0)

  4. True

注意: 具有不同值的對象也可能具有相同的哈希值(哈希沖突)。

當(dāng)執(zhí)行 somedict5 = 'Python' 語句時, 因為Python將 5 和 5.0 識別為 somedict 的同一個鍵, 所以已有值 'JavaScript' 就被 'Python' 覆蓋了。

到處返回!

  1. def some_func:

  2. try:

  3. return 'from_try'

  4. finally:

  5. return 'from_finally'


  6. Output:


  7. >>> some_func


  8. 'from_finally'

說明:

當(dāng)在 'try...finally' 語句的 try 中執(zhí)行 return, break 或 continue 后, finally 子句依然會執(zhí)行。

函數(shù)的返回值由最后執(zhí)行的 return 語句決定。

由于 finally 子句一定會執(zhí)行, 所以 finally 子句中的 return 將始終是最后執(zhí)行的語句。

本質(zhì)上,我們都一樣

  1. class WTF:

  2. pass


  3. Output:


  4. >>> WTF == WTF # 兩個不同的對象應(yīng)該不相等


  5. False


  6. >>> WTF is WTF # 也不相同


  7. False


  8. >>> hash(WTF) == hash(WTF) # 哈希值也應(yīng)該不同


  9. True


  10. >>> id(WTF) == id(WTF)


  11. True

說明:

當(dāng)調(diào)用 id 函數(shù)時, Python 創(chuàng)建了一個 WTF 類的對象并傳給 id 函數(shù)。

然后 id 函數(shù)獲取其id值 (也就是內(nèi)存地址), 然后丟棄該對象. 該對象就被銷毀了。

當(dāng)我們連續(xù)兩次進行這個操作時, Python會將相同的內(nèi)存地址分配給第二個對象。因為 (在CPython中) id 函數(shù)使用對象的內(nèi)存地址作為對象的id值, 所以兩個對象的id值是相同的。

綜上, 對象的id值僅僅在對象的生命周期內(nèi)唯一。在對象被銷毀之后, 或被創(chuàng)建之前, 其他對象可以具有相同的id值。

那為什么 is 操作的結(jié)果為 False 呢? 讓我們看看這段代碼:

  1. class WTF(object):

  2. def __init__(self): print('I')

  3. def __del__(self): print('D')


  4. Output:


  5. >>> WTF is WTF

  6. I

  7. I

  8. D

  9. D

  10. False

  11. >>> id(WTF) == id(WTF)

  12. I

  13. D

  14. I

  15. D

  16. True

正如你所看到的, 對象銷毀的順序是造成所有不同之處的原因。

為什么?

  1. some_string = 'wtf'

  2. some_dict = {}

  3. for i, some_dict[i] in enumerate(some_string):

  4. pass


  5. Output:


  6. >>> some_dict # 創(chuàng)建了索引字典.

  7. {0: 'w', 1: 't', 2: 'f'}

說明:

Python 語法 中對 for 的定義是:

  1. for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]

其中 exprlist 指分配目標(biāo). 這意味著對可迭代對象中的每一項都會執(zhí)行類似 {exprlist} = {next_value} 的操作。

一個有趣的例子說明了這一點:

  1. for i in range(4):

  2. print(i)

  3. i = 10


  4. Output:


  5. 0

  6. 1

  7. 2

  8. 3

你可曾覺得這個循環(huán)只會運行一次?

說明:

由于循環(huán)在Python中工作方式, 賦值語句 i = 10 并不會影響迭代循環(huán), 在每次迭代開始之前, 迭代器(這里指 range(4)) 生成的下一個元素就被解包并賦值給目標(biāo)列表的變量(這里指 i)了。

在每次迭代中, enumerate(somestring) 函數(shù)就生成一個新值 i (計數(shù)器增加) 并從 somestring 中獲取一個字符。

然后將字典 some_dict 鍵 i (剛剛分配的) 的值設(shè)為該字符。本例中循環(huán)的展開可以簡化為:

  1. >>> i, some_dict[i] = (0, 'w')

  2. >>> i, some_dict[i] = (1, 't')

  3. >>> i, some_dict[i] = (2, 'f')

  4. >>> some_dict

執(zhí)行時機差異

1.

  1. array = [1, 8, 15]

  2. g = (x for x in array if array.count(x) > 0)

  3. array = [2, 8, 22]


  4. Output:


  5. >>> print(list(g))

  6. [8]

2.

  1. array_1 = [1,2,3,4]

  2. g1 = (x for x in array_1)

  3. array_1 = [1,2,3,4,5]


  4. array_2 = [1,2,3,4]

  5. g2 = (x for x in array_2)

  6. array_2[:] = [1,2,3,4,5]


  7. Output:


  8. >>> print(list(g1))

  9. [1,2,3,4]


  10. >>> print(list(g2))

  11. [1,2,3,4,5]

說明:

在生成器表達式中, in 子句在聲明時執(zhí)行, 而條件子句則是在運行時執(zhí)行。

所以在運行前, array 已經(jīng)被重新賦值為 [2, 8, 22], 因此對于之前的 1, 8 和 15, 只有 count(8) 的結(jié)果是大于 0 的, 所以生成器只會生成 8。

第二部分中 g1 和 g2 的輸出差異則是由于變量 array1 和 array2 被重新賦值的方式導(dǎo)致的。

在第一種情況下, array_1 被綁定到新對象 [1,2,3,4,5], 因為 in 子句是在聲明時被執(zhí)行的,所以它仍然引用舊對象 1,2,3,4。

在第二種情況下, 對 array_2 的切片賦值將相同的舊對象 [1,2,3,4] 原地更新為 [1,2,3,4,5]。

因此 g2 和 array_2 仍然引用同一個對象(這個對象現(xiàn)在已經(jīng)更新為 [1,2,3,4,5])。

本文內(nèi)容來自中文版項目,項目全文2萬多字,以及海量代碼。

因為篇幅原因,小七就只為大家展示這6個案例了,更多案例大家可以在項目中查看。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多