超8千Star,火遍Github的Python反直覺案例集!

大資料文摘發表於2018-11-30

超8千Star,火遍Github的Python反直覺案例集!

大資料文摘授權轉載

作者:Satwik Kansal

譯者:暮晨

Python,是一個設計優美的解釋型高階語言,它提供了很多能讓程式設計師感到舒適的功能特性。

但有的時候,Python的一些輸出結果對於初學者來說似乎並不是那麼一目瞭然。

這個有趣的專案意在收集Python中那些難以理解和反人類直覺的例子以及鮮為人知的功能特性,並嘗試討論這些現象背後真正的原理!

雖然下面的有些例子並不一定會讓你覺得WTFs,但它們依然有可能會告訴你一些你所不知道的Python有趣特性。我覺得這是一種學習程式語言內部原理的好辦法,而且我相信你也會從中獲得樂趣!

如果你是一位經驗比較豐富的Python程式設計師,你可以嘗試挑戰看是否能一次就找到例子的正確答案。你可能對其中的一些例子已經比較熟悉了,那這也許能喚起你當年踩這些坑時的甜蜜回憶

超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!

示例結構

所有示例的結構都如下所示:

> 一個精選的標題 *

標題末尾的星號表示該示例在第一版中不存在,是最近新增的。

# 準備程式碼.

# 釋放魔法...

Output (Python version):

>>> 觸發語句

出乎意料的輸出結果

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

說明:

簡要說明發生了什麼以及為什麼會發生。

如有必要,舉例說明

Output:

>>>觸發語句#一些讓魔法變得容易理解的例子

#一些正常的輸入


注意:所有的示例都在Python3.5.2版本的互動直譯器上測試過,如果不特別說明應該適用於所有Python版本。

用法

我個人建議,最好依次閱讀下面的示例,並對每個示例:

仔細閱讀設定例子最開始的程式碼。如果您是一位經驗豐富的 Python 程式設計師,那麼大多數時候您都能成功預期到後面的結果。

閱讀輸出結果

  • 確認結果是否如你所料.

  • 確認你是否知道這背後的原理

PS: 你也可以在命令列閱讀 WTFpython. 我們有 pypi 包 和 npm 包(支援程式碼高亮).(譯: 這兩個都是英文版的)

安裝 npm 包 wtfpython

$ npm install -g wtfpython

或者, 安裝 pypi 包 wtfpython

$ pip install wtfpython -U

現在,在命令列中執行 wtfpython,你就可以開始瀏覽了。

示例

大腦運動!

微妙的字串*

1.

>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # 注意兩個的id值是相同的.
140420665652016

2.

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

>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b
False

>>> a, b = "wtf!", "wtf!"
>>> a is b
True

3.

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

說明:

這些行為是由於 Cpython 在編譯最佳化時,某些情況下會嘗試使用已經存在的不可變物件而不是每次都建立一個新物件。(這種行為被稱作字串的駐留[string interning])

發生駐留之後,許多變數可能指向記憶體中的相同字串物件。(從而節省記憶體)

在上面的程式碼中,字串是隱式駐留的。何時發生隱式駐留則取決於具體的實現。這裡有一些方法可以用來猜測字串是否會被駐留:

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

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

  • 字串中只包含字母,數字或下劃線時將會駐留。所以 'wtf!' 由於包含!而未被駐留。可以在這裡找CPython對此規則的實現。

超8千Star,火遍Github的Python反直覺案例集!


當在同一行將 a 和 b 的值設定為 "wtf!" 的時候, Python 直譯器會建立一個新物件, 然後同時引用第二個變數. 如果你在不同的行上進行賦值操作, 它就不會“知道”已經有一個 wtf! 物件 (因為 "wtf!" 不是按照上面提到的方式被隱式駐留的)。它是一種編譯器最佳化,特別適用於互動式環境。

常量摺疊(constant folding) 是 Python 中的一種窺孔最佳化(peephole optimization) 技術. 這意味著在編譯時表示式 'a'*20 會被替換為 'aaaaaaaaaaaaaaaaaaaa' 以減少執行時的時鐘週期. 只有長度小於 20 的字串才會發生常量摺疊。(為啥? 想象一下由於表示式'a'*10**10 而生成的 .pyc 檔案的大小). 相關的原始碼實現在這裡。

https://github.com/leisurelicht/wtfpython-cn/blob/master/images/string-intern/string_intern.png

是時候來點蛋糕了!

1.

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

Output:

>>> some_dict[5.5]
"Ruby"
>>> some_dict[5.0]
"Python"
>>> some_dict[5]
"Python"

"Python" 消除了 "JavaScript" 的存在?

說明:

Python 字典透過檢查鍵值是否相等和比較雜湊值來確定兩個鍵是否相同。

具有相同值的不可變物件在Python中始終具有相同的雜湊值。

>>> 5 == 5.0
True
>>> hash(5) == hash(5.0)
True

注意: 具有不同值的物件也可能具有相同的雜湊值(雜湊衝突)。

當執行 some_dict[5] = "Python" 語句時, 因為Python將 5 和 5.0 識別為 some_dict 的同一個鍵, 所以已有值 "JavaScript" 就被 "Python" 覆蓋了。

這個 StackOverflow的 回答 漂亮的解釋了這背後的基本原理。

到處返回!

def some_func():
   try:
       return 'from_try'
   finally:
       return 'from_finally'

Output:

>>> some_func()

'from_finally'

說明:

當在 "try...finally" 語句的 try 中執行 return, break 或 continue 後, finally 子句依然會執行。

函式的返回值由最後執行的 return 語句決定. 由於 finally 子句一定會執行, 所以 finally 子句中的 return 將始終是最後執行的語句。

本質上,我們都一樣. *

class WTF:
 pass

Output:

>>> WTF() == WTF() # 兩個不同的物件應該不相等

False

>>> WTF() is WTF() # 也不相同

False

>>> hash(WTF()) == hash(WTF()) # 雜湊值也應該不同

True

>>> id(WTF()) == id(WTF())

True

說明:

當呼叫 id 函式時, Python 建立了一個 WTF 類的物件並傳給 id 函式. 然後 id 函式獲取其id值 (也就是記憶體地址), 然後丟棄該物件. 該物件就被銷燬了.

當我們連續兩次進行這個操作時, Python會將相同的記憶體地址分配給第二個物件. 因為 (在CPython中) id 函式使用物件的記憶體地址作為物件的id值, 所以兩個物件的id值是相同的.

綜上, 物件的id值僅僅在物件的生命週期內唯一. 在物件被銷燬之後, 或被建立之前, 其他物件可以具有相同的id值.

那為什麼 is 操作的結果為 False 呢? 讓我們看看這段程式碼.

class WTF(object):
 def __init__(self): print("I")
 def __del__(self): print("D")

Output:

>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF()) == id(WTF())
I
D
I
D
True

正如你所看到的, 物件銷燬的順序是造成所有不同之處的原因。

為什麼?

some_string = "wtf"
some_dict = {}
for i, some_dict[i] in enumerate(some_string):
   pass

Output:

>>> some_dict # 建立了索引字典.
{0: 'w', 1: 't', 2: 'f'}

說明:

Python 語法 中對 for 的定義是:

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

其中 exprlist 指分配目標. 這意味著對可迭代物件中的每一項都會執行類似 {exprlist} = {next_value} 的操作。

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

for i in range(4):
   print(i)
   i = 10

Output:

0
1
2
3

你可曾覺得這個迴圈只會執行一次?

說明:

由於迴圈在Python中工作方式, 賦值語句 i = 10 並不會影響迭代迴圈, 在每次迭代開始之前, 迭代器(這裡指 range(4)) 生成的下一個元素就被解包並賦值給目標列表的變數(這裡指 i)了.

在每一次的迭代中, enumerate(some_string) 函式就生成一個新值 i (計數器增加) 並從 some_string 中獲取一個字元. 然後將字典 some_dict 鍵 i (剛剛分配的) 的值設為該字元. 本例中迴圈的展開可以簡化為:

>>> i, some_dict[i] = (0, 'w')
>>> i, some_dict[i] = (1, 't')
>>> i, some_dict[i] = (2, 'f')
>>> some_dict

評估時間差異

1.

array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]

Output:

>>> print(list(g))
[8]

2.

array_1 = [1,2,3,4]
g1 = (x for x in array_1)
array_1 = [1,2,3,4,5]

array_2 = [1,2,3,4]
g2 = (x for x in array_2)
array_2[:] = [1,2,3,4,5]

Output:

>>> print(list(g1))
[1,2,3,4]

>>> print(list(g2))
[1,2,3,4,5]

說明:

在生成器表示式中, in 子句在宣告時執行, 而條件子句則是在執行時執行.

所以在執行前, array 已經被重新賦值為 [2, 8, 22], 因此對於之前的 1, 8 和 15, 只有 count(8) 的結果是大於 0 的, 所以生成器只會生成 8.

第二部分中 g1 和 g2 的輸出差異則是由於變數 array_1 和 array_2 被重新賦值的方式導致的.

在第一種情況下, array_1 被繫結到新物件 [1,2,3,4,5], 因為 in 子句是在宣告時被執行的, 所以它仍然引用舊物件 [1,2,3,4](並沒有被銷燬).

在第二種情況下, 對 array_2 的切片賦值將相同的舊物件 [1,2,3,4] 原地更新為 [1,2,3,4,5]. 因此 g2 和 array_2 仍然引用同一個物件(這個物件現在已經更新為 [1,2,3,4,5]).

由於字數和排版受限,接下來的內容我們將用圖片的形式呈現,感興趣的同學請自行前往GitHub連結檢視原碼。

原文連結:

中文版:

%E5%A4%A7%E8%84%91%E8%BF%90%E5%8A%A8

超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!


超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!


超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

超8千Star,火遍Github的Python反直覺案例集!

最後,再次給出原文連結以及中文版:

%E5%A4%A7%E8%84%91%E8%BF%90%E5%8A%A8

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31562039/viewspace-2222258/,如需轉載,請註明出處,否則將追究法律責任。

相關文章