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