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