Python 的 51 个秘密曝光,Github 获 2 万星
點擊上方“小詹學Python”,選擇“置頂或者星標”
第一時間收到精彩推送!
Python, 是一個設計優美的解釋型高級語言, 它提供了很多能讓程序員感到舒適的功能特性. 但有的時候, Python 的一些輸出結果對于初學者來說似乎并不是那么一目了然.
一個解析51項堪稱是"秘密"的Python特性項目,在GitHub上徹底火了。
英文原版已經拿到了近15000星,中文翻譯版也獲得了7600+星。
這個有趣的項目意在收集 Python 中那些難以理解和反人類直覺的例子以及鮮為人知的功能特性, 并嘗試討論這些現象背后真正的原理!
雖然下面的有些例子并不一定會讓你覺得 WTFs, 但它們依然有可能會告訴你一些你所不知道的 Python 有趣特性. 我覺得這是一種學習編程語言內部原理的好辦法, 而且我相信你也會從中獲得樂趣!
如果您是一位經驗比較豐富的 Python 程序員, 你可以嘗試挑戰看是否能一次就找到例子的正確答案. 你可能對其中的一些例子已經比較熟悉了, 那這也許能喚起你當年踩這些坑時的甜蜜回憶?
這個項目的中文版全文大約2萬字,干貨多的快要溢出來了,大家可以先看一下目錄。
示例結構
所有示例的結構都如下所示:
我個人建議, 最好依次閱讀下面的示例, 并對每個示例:
仔細閱讀設置例子最開始的代碼. 如果您是一位經驗豐富的 Python 程序員, 那么大多數時候您都能成功預期到后面的結果.
閱讀輸出結果,
如果不知道, 深呼吸然后閱讀說明 (如果你還是看不明白, 別沉默! 可以在這提個 issue).
如果知道, 給自己點獎勵, 然后去看下一個例子.
確認結果是否如你所料.
確認你是否知道這背后的原理.
PS: 你也可以在命令行閱讀 WTFpython. 我們有 pypi 包 和 npm 包(支持代碼高亮).(譯: 這兩個都是英文版的)
示例
Strings can be tricky sometimes/微妙的字符串?
1、
>>> a = "some_string">>> id(a)140420665652016>>> id("some" + "_" + "string") # 注意兩個的id值是相同的.1404206656520162、
>>> a = "wtf">>> b = "wtf">>> a is bTrue>>> a = "wtf!">>> b = "wtf!">>> a is bFalse>>> a, b = "wtf!", "wtf!">>> a is b # 僅適用于3.7版本以下, 3.7以后的返回結果為False.True3、
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'True>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'False?說明:
這些行為是由于 Cpython 在編譯優化時, 某些情況下會嘗試使用已經存在的不可變對象而不是每次都創建一個新對象. (這種行為被稱作字符串的駐留[string interning])
發生駐留之后, 許多變量可能指向內存中的相同字符串對象. (從而節省內存)
在上面的代碼中, 字符串是隱式駐留的. 何時發生隱式駐留則取決于具體的實現. 這里有一些方法可以用來猜測字符串是否會被駐留:
所有長度為 0 和長度為 1 的字符串都被駐留.
字符串在編譯時被實現 ('wtf'?將被駐留, 但是?''.join(['w', 't', 'f']?將不會被駐留)
字符串中只包含字母,數字或下劃線時將會駐留. 所以?'wtf!'?由于包含?!?而未被駐留. 可以在這里找到 CPython 對此規則的實現.
當在同一行將?a?和?b?的值設置為?"wtf!"?的時候, Python 解釋器會創建一個新對象, 然后同時引用第二個變量(譯: 僅適用于3.7以下, 詳細情況請看這里). 如果你在不同的行上進行賦值操作, 它就不會“知道”已經有一個?wtf!?對象 (因為?"wtf!"?不是按照上面提到的方式被隱式駐留的). 它是一種編譯器優化, 特別適用于交互式環境.
常量折疊(constant folding) 是 Python 中的一種?窺孔優化(peephole optimization)?技術. 這意味著在編譯時表達式?'a'*20會被替換為?'aaaaaaaaaaaaaaaaaaaa'?以減少運行時的時鐘周期. 只有長度小于 20 的字符串才會發生常量折疊. (為啥? 想象一下由于表達式?'a'*10**10?而生成的?.pyc?文件的大小). 相關的源碼實現
https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288
Time for some hash brownies!/是時候來點蛋糕了!
hash brownie指一種含有大麻成分的蛋糕, 所以這里是句雙關
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中始終具有相同的哈希值.
注意:?具有不同值的對象也可能具有相同的哈希值(哈希沖突).
當執行?some_dict[5] = "Python"?語句時, 因為Python將?5?和?5.0?識別為?some_dict?的同一個鍵, 所以已有值 "JavaScript" 就被 "Python" 覆蓋了.
這個 StackOverflow的?回答?漂亮的解釋了這背后的基本原理.
https://stackoverflow.com/questions/32209155/why-can-a-floating-point-dictionary-key-overwrite-an-integer-key-with-the-same-v
Return return everywhere!/到處返回!
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?將始終是最后執行的語句.
?Deep down, we're all the same./本質上,我們都一樣.?
class WTF: passOutput:
>>> 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?呢? 讓我們看看這段代碼.
Output:
>>> WTF() is WTF()IIDDFalse>>> id(WTF()) == id(WTF())IDIDTrue????????正如你所看到的, 對象銷毀的順序是造成所有不同之處的原因.
For what?/為什么?
some_string = "wtf"some_dict = {}for i, some_dict[i] in enumerate(some_string): passOutput:
>>> 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 = 10Output:
0123
你可曾覺得這個循環只會運行一次?
?說明:
由于循環在Python中工作方式, 賦值語句?i = 10?并不會影響迭代循環, 在每次迭代開始之前, 迭代器(這里指?range(4)) 生成的下一個元素就被解包并賦值給目標列表的變量(這里指?i)了.
在每一次的迭代中,?enumerate(some_string)?函數就生成一個新值?i?(計數器增加) 并從?some_string?中獲取一個字符. 然后將字典?some_dict?鍵?i?(剛剛分配的) 的值設為該字符. 本例中循環的展開可以簡化為:
?Evaluation time discrepancy/執行時機差異
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]).
本文內容來自中文版項目,項目全文2萬多字,以及海量代碼。
因為篇幅原因,本文就只為大家展示這6個案例了,更多案例大家可以在項目中查看。
英文版項目名稱:wtfpython
鏈接:https://github.com/satwikkansal/wtfpython
中文項目名稱:wtfpython-cn
鏈接:https://github.com/leisurelicht/wtfpython-cn
猜你想讀:(點擊標題即可跳轉)
NB,用這一篇文章帶你了解什么是爬蟲?
總結
以上是生活随笔為你收集整理的Python 的 51 个秘密曝光,Github 获 2 万星的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 他因为泼了李彦宏一瓶水,成功圈粉无数,成
- 下一篇: 重磅!李宏毅教授机器学习训练营