python代数式的表达方式_关于python字典类型最疯狂的表达方式
一個(gè)Python字典表達(dá)式謎題
讓我們探究一下下面這個(gè)晦澀的python字典表達(dá)式,以找出在python解釋器的中未知的內(nèi)部到底發(fā)生了什么。
# 一個(gè)python謎題:這是一個(gè)秘密
# 這個(gè)表達(dá)式計(jì)算以后會(huì)得到什么結(jié)果?
>>>{True: 'yes', 1: 'no', 1.0: 'maybe'}
有時(shí)候你會(huì)碰到一個(gè)很有深度的代碼示例 --- 哪怕僅僅是一行代碼,但是如果你能夠有足夠的思考,它可以教會(huì)你很多關(guān)于編程語言的知識。這樣一個(gè)代碼片段,就像是一個(gè)Zen kōan:一個(gè)在修行的過程中用來質(zhì)疑和考驗(yàn)學(xué)生進(jìn)步的問題或陳述。
譯者注:Zen kōan,大概就是修行的一種方式,詳情見wikipedia
我們將在本教程中討論的小代碼片段就是這樣一個(gè)例子。乍看之下,它可能看起來像一個(gè)簡單的詞典表達(dá)式,但是仔細(xì)考慮時(shí),通過cpython解釋器,它會(huì)帶你進(jìn)行一次思維拓展的訓(xùn)練。
我從這個(gè)短短的一行代碼中得到了一個(gè)啟發(fā),而且有一次在我參加的一個(gè)Python會(huì)議上,我還把作為我演講的內(nèi)容,并以此開始演講。這也激發(fā)了我的python郵件列表成員間進(jìn)行了一些積極的交流。
所以不用多說,就是這個(gè)代碼片。花點(diǎn)時(shí)間思考一下下面的字典表達(dá)式,以及它計(jì)算后將得到的內(nèi)容:
>>>{True: 'yes', 1: 'no', 1.0: 'maybe'}
在這里,我先等會(huì)兒,大家思考一下...
5...
4...
3...
2...
1...
OK, 好了嗎?
這是在cpython解釋器交互界面中計(jì)算上述字典表達(dá)式時(shí)得到的結(jié)果:
>>>{True: 'yes', 1: 'no', 1.0: 'maybe'}
{True: 'maybe'}
我承認(rèn),當(dāng)我第一次看到這個(gè)結(jié)果時(shí),我很驚訝。但是當(dāng)你逐步研究其中發(fā)生的過程時(shí),這一切都是有道理的。所以,讓我們思考一下為什么我們得到這個(gè) -?我想說的是出乎意料?- 的結(jié)果。
這個(gè)子字典是從哪里來的
當(dāng)python處理我們的字典表達(dá)式時(shí),它首先構(gòu)造一個(gè)新的空字典對象;然后按照字典表達(dá)式給出的順序賦鍵和值。
因此,當(dāng)我們把它分解開的時(shí)候,我們的字典表達(dá)就相當(dāng)于這個(gè)順序的語句:
>>>xs = dict()
>>>xs[True] = 'yes'
>>>xs[1] = 'no'
>>>xs[1.0] = 'maybe'
奇怪的是,Python認(rèn)為在這個(gè)例子中使用的所有字典鍵是相等的:
>>>True == 1 == 1.0
True
OK,但在這里等一下。我確定你能夠接受1.0 == 1,但實(shí)際情況是為什么True也會(huì)被認(rèn)為等于1呢?我第一次看到這個(gè)字典表達(dá)式真的讓我難住了。
在python文檔中進(jìn)行一些探索之后,我發(fā)現(xiàn)python將bool作為了int類型的一個(gè)子類。這是在Python 2和Python 3的片段:
“The Boolean type is a subtype of the integer type, and Boolean values behave like the values 0 and 1, respectively, in almost all contexts, the exception being that when converted to a string, the strings ‘False’ or ‘True’ are returned, respectively.”
“布爾類型是整數(shù)類型的一個(gè)子類型,在幾乎所有的上下文環(huán)境中布爾值的行為類似于值0和1,例外的是當(dāng)轉(zhuǎn)換為字符串時(shí),會(huì)分別將字符串”False“或”True“返回。“(原文)
是的,這意味著你可以在編程時(shí)上使用bool值作為Python中的列表或元組的索引:
>>>['no', 'yes'][True]
'yes'
但為了代碼的可讀性起見,您不應(yīng)該類似這樣的來使用布爾變量。(也請建議你的同事別這樣做)
Anyway,讓我們回過來看我們的字典表達(dá)式。
就python而言,True,1和1.0都表示相同的字典鍵。當(dāng)解釋器計(jì)算字典表達(dá)式時(shí),它會(huì)重復(fù)覆蓋鍵True的值。這就解釋了為什么最終產(chǎn)生的字典只包含一個(gè)鍵。
在我們繼續(xù)之前,讓我們再回顧一下原始字典表達(dá)式:
>>>{True: 'yes', 1: 'no', 1.0: 'maybe'}
{True: 'maybe'}
這里為什么最終得到的結(jié)果是以True作為鍵呢?由于重復(fù)的賦值,最后不應(yīng)該是把鍵也改為1.0了?經(jīng)過對cpython解釋器源代碼的一些模式研究,我知道了,當(dāng)一個(gè)新的值與字典的鍵關(guān)聯(lián)的時(shí)候,python的字典不會(huì)更新鍵對象本身:
>>>ys = {1.0: 'no'}
>>>ys[True] = 'yes'
>>>ys
{1.0: 'yes'}
當(dāng)然這個(gè)作為性能優(yōu)化來說是有意義的 --- 如果鍵被認(rèn)為是相同的,那么為什么要花時(shí)間更新原來的?在最開始的例子中,你也可以看到最初的True對象一直都沒有被替換。因此,字典的字符串表示仍然打印為以True為鍵(而不是1或1.0)。
就目前我們所知而言,似乎看起來像是,結(jié)果中字典的值一直被覆蓋,只是因?yàn)樗麄兊逆I比較后相等。然而,事實(shí)上,這個(gè)結(jié)果也不單單是由__eq__比較后相等就得出的。
等等,那哈希值呢?
python字典類型是由一個(gè)哈希表數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)的。當(dāng)我第一次看到這個(gè)令人驚訝的字典表達(dá)式時(shí),我的直覺是這個(gè)結(jié)果與散列沖突有關(guān)。
哈希表中鍵的存儲(chǔ)是根據(jù)每個(gè)鍵的哈希值的不同,包含在不同的“buckets”中。哈希值是指根據(jù)每個(gè)字典的鍵生成的一個(gè)固定長度的數(shù)字串,用來標(biāo)識每個(gè)不同的鍵。(哈希函數(shù)詳情)
這可以實(shí)現(xiàn)快速查找。在哈希表中搜索鍵對應(yīng)的哈希數(shù)字串會(huì)快很多,而不是將完整的鍵對象與所有其他鍵進(jìn)行比較,來檢查互異性。
然而,通常計(jì)算哈希值的方式并不完美。并且,實(shí)際上會(huì)出現(xiàn)不同的兩個(gè)或更多個(gè)鍵會(huì)生成相同的哈希值,并且它們最后會(huì)出現(xiàn)在相同的哈希表中。
如果兩個(gè)鍵具有相同的哈希值,那就稱為哈希沖突(hash collision),這是在哈希表插入和查找元素時(shí)需要處理的特殊情況。
基于這個(gè)結(jié)論,哈希值與我們從字典表達(dá)中得到的令人意外的結(jié)果有很大關(guān)系。所以讓我們來看看鍵的哈希值是否也在這里起作用。
我定義了這樣一個(gè)類來作為我們的測試工具:
classAlwaysEquals:
def__eq__(self,other):
return True
def__hash__(self):
return id(self)
這個(gè)類有兩個(gè)特別之處。
第一,因?yàn)樗腳_eq__魔術(shù)方法(譯者注:雙下劃線開頭雙下劃線結(jié)尾的是一些Python的“魔術(shù)”對象)總是返回true,所以這個(gè)類的所有實(shí)例和其他任何對象都會(huì)恒等:
>>>AlwaysEquals() == AlwaysEquals()
True
>>>AlwaysEquals() == 42
True
>>>AlwaysEquals() == 'waaat?'
True
第二,每個(gè)Alwaysequals實(shí)例也將返回由內(nèi)置函數(shù)id()生成的唯一哈希值值:
>>>objects = [AlwaysEquals(),
AlwaysEquals(),
AlwaysEquals()]
>>>[hash(obj) for obj in objects]
[4574298968, 4574287912, 4574287072]
在CPython中,id()函數(shù)返回的是一個(gè)對象在內(nèi)存中的地址,并且是確定唯一的。
通過這個(gè)類,我們現(xiàn)在可以創(chuàng)建看上去與其他任何對象相同的對象,但它們都具有不同的哈希值。我們就可以通過這個(gè)來測試字典的鍵是否是基于它們的相等性比較結(jié)果來覆蓋。
正如你所看到的,下面的一個(gè)例子中的鍵不會(huì)被覆蓋,即使它們總是相等的:
>>>{AlwaysEquals(): 'yes', AlwaysEquals(): 'no'}
{ : 'yes',
: 'no' }
下面,我們可以換個(gè)思路,如果返回相同的哈希值是不是就會(huì)讓鍵被覆蓋呢?
classSameHash:
def__hash__(self):
return 1
這個(gè)SameHash類的實(shí)例將相互比較一定不相等,但它們會(huì)擁有相同的哈希值1:
>>>a = SameHash()
>>>b = SameHash()
>>>a == b
False
>>>hash(a), hash(b)
(1, 1)
一起來看看python的字典在我們試圖使用SameHash類的實(shí)例作為字典鍵時(shí)的結(jié)果:
>>>{a: 'a', b: 'b'}
{ : 'a',
: 'b' }
如本例所示,“鍵被覆蓋”的結(jié)果也并不是單獨(dú)由哈希沖突引起的。
Umm..好吧,可以得到什么結(jié)論呢?
檢查python字典對象中兩個(gè) key 是否相同的條件是:二者的相等性(__eq__)以及hash值對比(__hash__)是否相等。那我們就來總結(jié)一下上述討論的結(jié)果:
{true:'yes',1:'no',1.0:'maybe'}?字典表達(dá)式計(jì)算結(jié)果為?{true:'maybe'},是因?yàn)殒I?true,1?和?1.0?都是相等的,并且它們都有相同的哈希值:
>>>True == 1 == 1.0
True
>>>(hash(True), hash(1), hash(1.0))
(1, 1, 1)
也許并不那么令人驚訝,這就是我們?yōu)楹蔚玫竭@個(gè)結(jié)果作為字典的最終結(jié)果的原因:
>>>{True: 'yes', 1: 'no', 1.0: 'maybe'}
{True: 'maybe'}
我們在這里涉及了很多方面內(nèi)容,而這個(gè)特殊的python技巧起初可能有點(diǎn)令人難以置信 --- 所以我一開始就把它比作是Zen kōan。
如果很難理解本文中的內(nèi)容,請嘗試在Python交互環(huán)境中逐個(gè)去檢驗(yàn)一下代碼示例。你會(huì)收獲一些關(guān)于python深處知識。
總結(jié)
以上是生活随笔為你收集整理的python代数式的表达方式_关于python字典类型最疯狂的表达方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php console postman,
- 下一篇: Python爬虫用Selenium抓取j