一些有趣且鮮為人知的 Python 特性
Output (Python version):
>>> 觸發(fā)語(yǔ)句
出乎意料的輸出結(jié)果(可選): 對(duì)意外輸出結(jié)果的簡(jiǎn)短描述.
?? 說(shuō)明:
簡(jiǎn)要說(shuō)明發(fā)生了什么以及為什么會(huì)發(fā)生.
如有必要, 舉例說(shuō)明Output:
>>> 觸發(fā)語(yǔ)句 # 一些讓魔法變得容易理解的例子# 一些正常的輸入
注意: 所有的示例都在 Python 3.5.2 版本的交互解釋器上測(cè)試過(guò), 如果不特別說(shuō)明應(yīng)該適用于所有 Python 版本.
我個(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è)都是英文版的)
安裝 npm 包 wtfpython
$ npm install -g wtfpython
或者, 安裝 pypi 包 wtfpython
$ pip install wtfpython -U
現(xiàn)在, 在命令行中運(yùn)行 wtfpython, 你就可以開(kāi)始瀏覽了.
Section: Strain your brain!/大腦運(yùn)動(dòng)!
> Strings can be tricky sometimes/微妙的字符串 *
1.
>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string")# 注意兩個(gè)的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.7 版本返回結(jié)果為 False.
3.
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False # 3.7 版本返回結(jié)果為 True
很好理解, 對(duì)吧?
?? 說(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ù)字或下劃線(xiàn)時(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)?nbsp;"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)在這里.如果你是使用 3.7 版本中運(yùn)行上述示例代碼, 會(huì)發(fā)現(xiàn)部分代碼的運(yùn)行結(jié)果與注釋說(shuō)明相同. 這是因?yàn)樵?3.7 版本中, 常量折疊已經(jīng)從窺孔優(yōu)化器遷移至新的 AST 優(yōu)化器, 后者可以以更高的一致性來(lái)執(zhí)行優(yōu)化. (由 Eugene Toder 和 INADA Naoki 在 bpo-29469 和 bpo-11549 中貢獻(xiàn).)
(譯: 但是在最新的 3.8 版本中, 結(jié)果又變回去了. 雖然 3.8 版本和 3.7 版本一樣, 都是使用 AST 優(yōu)化器. 目前不確定官方對(duì) 3.8 版本的 AST 做了什么調(diào)整.)
> 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中始終具有相同的哈希值.
>>> 5 == 5.0
True
>>> hash(5) == hash(5.0)
True
注意: 具有不同值的對(duì)象也可能具有相同的哈希值(哈希沖突).
當(dāng)執(zhí)行
some_dict[5] = "Python"語(yǔ)句時(shí), 因?yàn)镻ython將5和5.0識(shí)別為some_dict的同一個(gè)鍵, 所以已有值 "JavaScript" 就被 "Python" 覆蓋了.這個(gè) StackOverflow的 回答 漂亮地解釋了這背后的基本原理.
> 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:
pass
Output:
>>> WTF() == WTF() # 兩個(gè)不同的對(duì)象應(yīng)該不相等
False
>>> WTF() is WTF() # 也不相同
False
>>> 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呢? 讓我們看看這段代碼.
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
正如你所看到的, 對(duì)象銷(xiāo)毀的順序是造成所有不同之處的原因.
> For what?/為什么?
some_string = "wtf"
some_dict = {}
for i, some_dict[i] in enumerate(some_string):
pass
Output:
>>> 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 = 10
Output:
0
1
2
3
你可曾覺(jué)得這個(gè)循環(huán)只會(huì)運(yùn)行一次?
由于循環(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)化為:
>>> i, some_dict[i] = (0, 'w')
>>> i, some_dict[i] = (1, 't')
>>> i, some_dict[i] = (2, 'f')
>>> some_dict


