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

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

2018-12-04    來源:raincent

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬Linux鏡像隨意使用

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

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

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

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

更新說明:https://github.com/satwikkansal/wtfpython/releases/
項(xiàng)目地址: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)都如下所示:

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

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

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

Output (Python version):

>>> 觸發(fā)語句

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

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

說明:

簡(jiǎn)要說明發(fā)生了什么以及為什么會(huì)發(fā)生。

如有必要, 舉例說明

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

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

Usage/用法

我個(gè)人建議,最好依次閱讀下面的示例,并對(duì)每個(gè)示例:

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

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

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

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

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

如果知道,給自己點(diǎn)獎(jiǎng)勵(lì),然后去看下一個(gè)例子。

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

安裝 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)在, 在命令行中運(yùn)行 wtfpython, 你就可以開始瀏覽了.

Examples/示例

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

 

很好理解, 對(duì)吧?

說明:

♦ 這些行為是由于 Cpython 在編譯優(yōu)化時(shí), 某些情況下會(huì)嘗試使用已經(jīng)存在的不可變對(duì)象而不是每次都創(chuàng)建一個(gè)新對(duì)象。(這種行為被稱作字符串的駐留 [string interning])

♦ 發(fā)生駐留之后, 許多變量可能指向內(nèi)存中的相同字符串對(duì)象。(從而節(jié)省內(nèi)存)

♦ 在上面的代碼中, 字符串是隱式駐留的. 何時(shí)發(fā)生隱式駐留則取決于具體的實(shí)現(xiàn). 這里有一些方法可以用來猜測(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è)變量。如果你在不同的行上進(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í)候來點(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" 的存在?

說明:

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

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

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

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

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

♦ 這個(gè) 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 子句依然會(huì)執(zhí)行。

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

> 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

說明:

♦ 當(dāng)調(diào)用 id 函數(shù)時(shí),Python 創(chuàng)建了一個(gè) WTF 類的對(duì)象并傳給 id 函數(shù)。然后 id 函數(shù)獲取其 id 值 (也就是內(nèi)存地址),然后丟棄該對(duì)象。該對(duì)象就被銷毀了。

♦ 當(dāng)我們連續(xù)兩次進(jìn)行這個(gè)操作時(shí),Python 會(huì)將相同的內(nèi)存地址分配給第二個(gè)對(duì)象。因?yàn)?(在 CPython 中) id 函數(shù)使用對(duì)象的內(nèi)存地址作為對(duì)象的 id 值,所以兩個(gè)對(duì)象的 id 值是相同的。

♦ 綜上,對(duì)象的 id 值僅僅在對(duì)象的生命周期內(nèi)唯一。在對(duì)象被銷毀之后,或被創(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
DFalse>>> id(WTF()) == id(WTF())
I
D
I
DTrue

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

> 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 語法 中對(duì) for 的定義是:

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

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

一個(gè)有趣的例子說明了這一點(diǎn):

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

 

Output:

0
1
2
3

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

說明:

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

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

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

 

> Evaluation time discrepancy/評(píng)估時(shí)間差異

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]

說明

♦ 在生成器表達(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](并沒有被銷毀)。

♦ 在第二種情況下,對(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])。

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

下面是一個(gè)在互聯(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ùn)算符檢查兩個(gè)運(yùn)算對(duì)象是否引用自同一對(duì)象 (即, 它檢查兩個(gè)預(yù)算對(duì)象是否相同)。

♦ == 運(yùn)算符比較兩個(gè)運(yùn)算對(duì)象的值是否相等。

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

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

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

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

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

當(dāng)前的實(shí)現(xiàn)為-5 到 256 之間的所有整數(shù)保留一個(gè)整數(shù)對(duì)象數(shù)組, 當(dāng)你創(chuàng)建了一個(gè)該范圍內(nèi)的整數(shù)時(shí), 你只需要返回現(xiàn)有對(duì)象的引用. 所以改變 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 時(shí)意識(shí)到我們已經(jīng)創(chuàng)建了一個(gè)整數(shù) 257,所以它在內(nèi)存中又新建了另一個(gè)對(duì)象。

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

>>> 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 時(shí), Python 解釋器會(huì)創(chuàng)建一個(gè)新對(duì)象, 然后同時(shí)引用第二個(gè)變量. 如果你在不同的行上進(jìn)行, 它就不會(huì) "知道" 已經(jīng)存在一個(gè) 257 對(duì)象了。

♦ 這是一種特別為交互式環(huán)境做的編譯器優(yōu)化. 當(dāng)你在實(shí)時(shí)解釋器中輸入兩行的時(shí)候, 他們會(huì)單獨(dú)編譯, 因此也會(huì)單獨(dú)進(jìn)行優(yōu)化. 如果你在 .py 文件中嘗試這個(gè)例子, 則不會(huì)看到相同的行為, 因?yàn)槲募且淮涡跃幾g的。

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

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

Output:

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

我們有沒有賦值過 3 個(gè) "X" 呢?

說明:

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

 

 

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

 

 

我們可以通過不使用變量 row 生成 board 來避免這種情況. (這個(gè) issue 提出了這個(gè)需求(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。

// 再換個(gè)例子

>>> 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)部定義一個(gè)函數(shù)時(shí),如果該函數(shù)在其主體中使用了循環(huán)變量,則閉包函數(shù)將與循環(huán)變量綁定,而不是它的值。因此,所有的函數(shù)都是使用最后分配給變量的值來進(jìn)行計(jì)算的。

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

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 是個(gè)單獨(dú)的二進(jìn)制運(yùn)算符,和分別使用 is 和 not 不同。

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

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

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

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

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

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

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