中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

WTF Python:有趣且鮮為人知的Python特性

2018-12-04    來源:raincent

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

Python 是一個設(shè)計優(yōu)美的解釋型高級語言,它提供了很多能讓程序員感到舒適的功能特性。但有的時候,Python的一些輸出結(jié)果對于初學(xué)者來說似乎并不是那么一目了然。 這個有趣的項目意在收集 Python 中那些難以理解和反人類直覺的例子以及鮮為人知的功能特性,并嘗試討論這些現(xiàn)象背后真正的原理!

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

如果您是一位經(jīng)驗比較豐富的 Python 程序員,你可以嘗試挑戰(zhàn)看是否能一次就找到例子的正確答案。你可能對其中的一些例子已經(jīng)比較熟悉了,那這也許能喚起你當(dāng)年踩這些坑時的甜蜜回憶~

如果你不是第一次讀了, 你可以在這里獲取變動內(nèi)容.

更新說明:https://github.com/satwikkansal/wtfpython/releases/
項目地址:https://github.com/leisurelicht/wtfpython-cn
英文版:https://github.com/satwikkansal/wtfpython
pdf 版:http://www.satwikkansal.xyz/wtfpython-pdf/

那么,讓我們開始吧...

 

 

 

 

 

 

 

 

Structure of the Examples/示例結(jié)構(gòu)

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

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

標(biāo)題末尾的星號表示該示例在第一版中不存在,是最近添加的。

# 準(zhǔn)備代碼.# 釋放魔法...

Output (Python version):

>>> 觸發(fā)語句

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

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

說明:

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

如有必要, 舉例說明

Output:
>>> 觸發(fā)語句 # 一些讓魔法變得容易理解的例子# 一些正常的輸入

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

Usage/用法

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

♦ 仔細(xì)閱讀設(shè)置例子最開始的代碼。如果您是一位經(jīng)驗豐富的 Python 程序員,那么大多數(shù)時候您都能成功預(yù)期到后面的結(jié)果。

♦ 閱讀輸出結(jié)果,

♦ 確認(rèn)結(jié)果是否如你所料。

♦ 確認(rèn)你是否知道這背后的原理。

♦ 如果不知道, 深呼吸然后閱讀說明 (如果你還是看不明白, 別沉默!可以在這 (https://github.com/satwikkansal/wtfPython) 提個 issue)。

如果知道,給自己點獎勵,然后去看下一個例子。

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

安裝 npm 包 wtfpython(https://www.npmjs.com/package/wtfpython)

$ npm install -g wtfpython

或者, 安裝 pypi 包 wtfpython(https://pypi.python.org/pypi/wtfpython)

$ pip install wtfpython -U

現(xiàn)在, 在命令行中運行 wtfpython, 你就可以開始瀏覽了.

Examples/示例

Section: Strain your brain!/大腦運動!

> Strings can be tricky sometimes/微妙的字符串 *

1.

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

2.

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

3.

>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
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)。

 

 

♦ 當(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)的源碼實現(xiàn)在這里(https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288)。

> Time for some hash brownies!/是時候來點蛋糕了!

♦ 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" 的存在?

說明:

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

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

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

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

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

♦ 這個 StackOverflow 的回答(https://stackoverflow.com/a/32211042/4354153)漂亮的解釋了這背后的基本原理.

> Return return everywhere!/到處返回!

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

Output:

>>> some_func()
'from_finally'

說明:

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

♦ 函數(shù)的返回值由最后執(zhí)行的 return 語句決定。由于 finally 子句一定會執(zhí)行,所以 finally 子句中的 return 將始終是最后執(zhí)行的語句。

> Deep down, we're all the same./本質(zhì)上, 我們都一樣. *

class WTF:
pass

Output:

>>> WTF() == WTF() # 兩個不同的對象應(yīng)該不相等
False
>>> WTF() is WTF() # 也不相同
False
>>> hash(WTF()) == hash(WTF()) # 哈希值也應(yīng)該不同
True
>>> id(WTF()) == id(WTF())
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 呢?讓我們看看這段代碼。

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

Output:

>>> WTF() is WTF()
I
I
D
DFalse>>> id(WTF()) == id(WTF())
I
D
I
DTrue

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

> 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'}

說明:

♦ Python 語法 中對 for 的定義是:

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

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

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

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

 

Output:

0
1
2
3

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

說明:

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

♦ 在每一次的迭代中, enumerate(some_string) 函數(shù)就生成一個新值 i (計數(shù)器增加) 并從 some_string 中獲取一個字符. 然后將字典 some_dict 鍵 i (剛剛分配的) 的值設(shè)為該字符. 本例中循環(huán)的展開可以簡化為:

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

 

> 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 子句在聲明時執(zhí)行,而條件子句則是在運行時執(zhí)行。

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

♦ 第二部分中 g1 和 g2 的輸出差異則是由于變量 array_1 和 array_2 被重新賦值的方式導(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])。

> is is not what it is!/出人意料的 is!

下面是一個在互聯(lián)網(wǎng)上非常有名的例子.

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

>>> a = 257; b = 257
>>> a is b
True

說明:

is 和 == 的區(qū)別

♦ is 運算符檢查兩個運算對象是否引用自同一對象 (即, 它檢查兩個預(yù)算對象是否相同)。

♦ == 運算符比較兩個運算對象的值是否相等。

♦ 因此 is 代表引用相同,== 代表值相等。下面的例子可以很好的說明這點,

>>> [] == []
True
>>> [] is [] # 這兩個空列表位于不同的內(nèi)存地址.
False

256 是一個已經(jīng)存在的對象, 而 257 不是

當(dāng)你啟動 Python 的時候, -5 到 256 的數(shù)值就已經(jīng)被分配好了。這些數(shù)字因為經(jīng)常使用所以適合被提前準(zhǔn)備好。

引用自 https://docs.python.org/3/c-api/long.html

當(dāng)前的實現(xiàn)為-5 到 256 之間的所有整數(shù)保留一個整數(shù)對象數(shù)組, 當(dāng)你創(chuàng)建了一個該范圍內(nèi)的整數(shù)時, 你只需要返回現(xiàn)有對象的引用. 所以改變 1 的值是有可能的. 我懷疑這種行為在 Python 中是未定義行為. :-)

>>> id(256)
10922528
>>> a = 256
>>> b = 256
>>> id(a)
10922528
>>> id(b)
10922528
>>> id(257)
140084850247312
>>> x = 257
>>> y = 257
>>> id(x)
140084850247440
>>> id(y)
140084850247344

這里解釋器并沒有智能到能在執(zhí)行 y = 257 時意識到我們已經(jīng)創(chuàng)建了一個整數(shù) 257,所以它在內(nèi)存中又新建了另一個對象。

當(dāng) a 和 b 在同一行中使用相同的值初始化時,會指向同一個對象。

>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488

♦ 當(dāng) a 和 b 在同一行中被設(shè)置為 257 時, Python 解釋器會創(chuàng)建一個新對象, 然后同時引用第二個變量. 如果你在不同的行上進行, 它就不會 "知道" 已經(jīng)存在一個 257 對象了。

♦ 這是一種特別為交互式環(huán)境做的編譯器優(yōu)化. 當(dāng)你在實時解釋器中輸入兩行的時候, 他們會單獨編譯, 因此也會單獨進行優(yōu)化. 如果你在 .py 文件中嘗試這個例子, 則不會看到相同的行為, 因為文件是一次性編譯的。

> A tic-tac-toe where X wins in the first attempt!/一蹴即至!

# 我們先初始化一個變量row
row = [""]*3 #row i['', '', '']
# 并創(chuàng)建一個變量board
board = [row]*3

Output:

>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]

我們有沒有賦值過 3 個 "X" 呢?

說明:

當(dāng)我們初始化 row 變量時, 下面這張圖展示了內(nèi)存中的情況。

 

 

而當(dāng)通過對 row 做乘法來初始化 board 時, 內(nèi)存中的情況則如下圖所示 (每個元素 board[0], board[1] 和 board[2] 都和 row 一樣引用了同一列表.)

 

 

我們可以通過不使用變量 row 生成 board 來避免這種情況. (這個 issue 提出了這個需求(https://github.com/satwikkansal/wtfpython/issues/68)

>>> board = [['']*3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]

> The sticky output function/麻煩的輸出

funcs = []
results = []
for x in range(7):
def some_func():
return x
funcs.append(some_func)
results.append(some_func())

funcs_results = [func() for func in funcs]

Output:

>>> results
[0, 1, 2, 3, 4, 5, 6]
>>> funcs_results
[6, 6, 6, 6, 6, 6, 6]

即使每次在迭代中將 some_func 加入 funcs 前的 x 值都不相同, 所有的函數(shù)還是都返回 6。

// 再換個例子

>>> powers_of_x = [lambda x: x**i for i in range(10)]
>>> [f(2) for f in powers_of_x]
[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

說明:

♦ 當(dāng)在循環(huán)內(nèi)部定義一個函數(shù)時,如果該函數(shù)在其主體中使用了循環(huán)變量,則閉包函數(shù)將與循環(huán)變量綁定,而不是它的值。因此,所有的函數(shù)都是使用最后分配給變量的值來進行計算的。

♦ 可以通過將循環(huán)變量作為命名變量傳遞給函數(shù)來獲得預(yù)期的結(jié)果。為什么這樣可行?因為這會在函數(shù)內(nèi)再次定義一個局部變量。

funcs = []
for x in range(7):
def some_func(x=x):
return x
funcs.append(some_func)

Output:

>>> funcs_results = [func() for func in funcs]
>>> funcs_results
[0, 1, 2, 3, 4, 5, 6]

> is not ... is not is (not ...)/is not ... 不是 is (not ...)

>>> 'something' is not None
True
>>> 'something' is (not None)
False

說明:

♦ is not 是個單獨的二進制運算符,和分別使用 is 和 not 不同。

♦ 如果操作符兩側(cè)的變量指向同一個對象,則 is not 的結(jié)果為 False, 否則結(jié)果為 True。

本文中摘錄了該項目中約 1/4 的例子,更多內(nèi)容請查看原項目頁:

♦ https://github.com/leisurelicht/wtfpython-cn

標(biāo)簽: 代碼 互聯(lián)網(wǎng)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:吳恩達《機器學(xué)習(xí)》筆記,哥大研究生獻上

下一篇:實時流處理新選擇:LinkedIn 重磅發(fā)布 Samza 1.0