天天看點

在 Python 中,三個關于可哈希不可不知的問題

雲栖号資訊:【 點選檢視更多行業資訊

在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

作為一種通用的程式設計語言,Python 為不同的使用者場景提供了一系列内置的資料結構。

當你學習 Python 基礎知識的時候,你可能在某些地方看到有提及可哈希。例如,你可能會看到 dict 中的鍵需要是可哈希的(請參見下面代碼片段中的一個小示例)。

在另一個例子中,它提到了 set 中的元素需要是可哈希的。

>>> # 一個正确的字典聲明
>>> good_dict = {"a": 1, "b": 2}
>>> 
>>> # 一個錯誤的字典聲明
>>> failed_dict = {["a"]: 1, ["b"]: 2}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'           

你可能會想知道 —— 可哈希到底是什麼意思?哪些對象是可哈希的,而哪些又不是?如果我們使用不可哈希的對象作為字典的鍵會發生什麼?諸如此類,你可能還想問很多相關的問題。

在本文中,我們将讨論關于哈希的一些基本的知識點,以讓你可以解決上述的這些問題。最後你可能會發現這些問題一點都不像你開始想的那麼難。

哪些對象是可哈希的,而哪些又不是?

當我們展開關于哈希的機制解釋之前,第一個需要解決的問題就是哪些對象是可哈希的,而哪些又不是。

因為我們知道 Python 顯式的要求能夠加入 set 的元素應該是可哈希的,是以我們可以通過測試是否可以将一個對象加入 set 來判斷它的哈希屬性。成功插入表示對象是可以被哈希的,反之亦然。

>>> # 建立一個空的集合
>>> elements = set()
>>> 
>>> # 将各種類型的對象插入到這個集合中
>>> items = [1, 0.1, 'ab', (2, 3), {'a': 1}, [1, 2], {2, 4}, None]           

如上述代碼所示,我建立了一個命名為 elements 的 set 變量,以及另一個命名為 items 的 list 變量,items 中包含了最常用的内置資料類型:int、float、str、tuple、dict、list、set 和 NoneType。

我将進行的實驗是将 items 中的每一個元素添加到 elements 中。在這個場景下我不會使用 for 循環,因為任何一個可能的 TypeError 都會導緻疊代的中止。相反,我們會是根據索引來疊代每一項。

>>> elements.add(items[0])
>>> elements.add(items[1])
>>> elements.add(items[2])
>>> elements.add(items[3])
>>> elements.add(items[4])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> elements.add(items[5])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> elements.add(items[6])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
>>> elements.add(items[7])
>>> elements
{0.1, 1, 'abc', None, (2, 3)}           

正如你在如上代碼片段所看到的,這裡對實驗結果做出了一個簡單的總結。

對本節問題的回答

  • 可以被哈希的資料結構:int、float、str、tuple 和 NoneType。
  • 不可以被哈希的資料結構:dict、list 和 set。

即使你對 Python 程式設計還不太熟悉,但你可能也已經注意到這三種不可哈希的資料結構在實際上都是可變的,而這五種可哈希的資料結構都是不可變的。

本質上,這些可變的資料結構是指其這個對象在建立後可以被更改,而不可變對象的值在建立後不能被更改。

可哈希意味着什麼?

現在你已經知道哪些對象是可以被哈希的,哪些不是,但是哈希到底是什麼意思呢?

實際上,你可能聽說過許多類似的計算機術語都與哈希相關,如哈希值、哈希化、哈希表和哈希圖。它們的核心在于它們有着相同的基本過程 —— 哈希化。

在 Python 中,三個關于可哈希不可不知的問題

上圖展示了哈希化的一般過程。我們從一些原始資料值(圖中稱為 key)開始。

哈希函數有時被稱為哈希器,它将執行特定的計算并輸出原始資料值的哈希值(在圖中稱為 hashes)。

哈希化及其相關概念需要一整本書來闡述,那超出了本文的範圍。不過,我在我的 上一篇文章 中簡要讨論了一些重要方面。

在這裡,我将強調與本次讨論相關的一些要點。

  • 哈希函數應該是計算健壯的,以使不同的對象得到不同的哈希值。當不同的對象具有相同的哈希值時就會發生沖突(如上圖所示),并應該進行處理。
  • 哈希函數還應該是具有一緻性,以使相同的對象将始終得到相同的哈希值。

Python 已經實作了内置哈希函數來為它的對象生成哈希值。具體來說,我們可以使用内置的 hash() 函數來找到對象的哈希值。下面的代碼将向你展示一些示例。

>>> # 得到一個字元串對象的哈希值
>>> hash("Hello World!")
5892830027481816904
>>> 
>>> # 得到一個元組對象的哈希值
>>> hash((2, 'Hello'))
-4798835419149360612
>>> 
>>> # 得到一個清單對象的哈希值
>>> hash([1, 2, 3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> 
>>> # 得到一個字典對象的哈希值
>>> hash({"a": 1, "b": 2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'           

正如上述代碼所示,我們可以為 int 和 tuple 類型得到哈希值 —— 整數串。

但是,list 對象和 dict 對象都沒有哈希值,這些結果與我們在上一節中對可哈希對象和不可哈希對象所做的區分是一緻的。

是否可哈希:Python 對象的一個特性,用于判斷對象是否具有哈希值,這将是對象充當字典鍵或集合中元素的必要條件。

我們可以如何定制哈希屬性?

Python 作為通用程式設計語言的靈活性主要來自于它對建立自定義類的支援。有了自己的類,許多相關的資料和操作可以用更有意義和可讀性的方式歸類。

而且重要的是,Python 已經逐漸發展到足夠聰明,可以在大多數情況下使我們的自定義對象預設是可哈希的。

正如下面的例子。我們建立了一個自定義類 Person,它允許我們通過指定一個人的姓名和社會保險号碼來建立執行個體。

同時值得注意的是,我們使用 f-string 方法重寫了預設的 __repr__() 函數,這将允許我們用更加可讀的資訊顯示對象,就像代碼最後一行所展示的那樣。

>>> # 建立一個自定義類
>>> class Person:
...     def __init__(self, name, ssn):
...         self.name = name
...         self.ssn = ssn
...
...     def __repr__(self):
...         return f"***Name: {self.name}; SSN: {self.ssn}***"
... 
>>> # 建立一個自定義執行個體并檢查哈希值
>>> person0 = Person('John Smith', '012345678')
>>> hash(person0)
272583189
>>> 
>>> # 建立一個包含這個 Person 對象的集合
>>> persons = {person0}
>>> persons
{***Name: John Smith; SSN: 012345678***}           

如上面的代碼所示,我們可以使用内置的 hash() 函數來查找建立的對象 person0 的哈希值。而且重要的是,我們可以将 person0 對象作為元素包含在 set 對象中,這很棒哦。

但是,如果我們想向集合中添加更多的 Person 執行個體,會發生什麼情況呢?一個更複雜但可能的場景是,我們構造同一個人的多個 Person 對象,并嘗試将它們添加到 set 對象中。

請參閱如下代碼。我建立了另一個 Person 執行個體 person1,它具有相同的名稱和社會保險号碼 — 本質上是相同的自然人。

>>> # 建立了另外一個相同的 Person 對象
>>> person1 = Person('John Smith', '012345678')
>>> 
>>> # 将這個 person1 加入到集合中
>>> persons.add(person1)
>>> persons
{***Name: John Smith; SSN: 012345678***, ***Name: John Smith; SSN: 012345678***}
>>> 
>>> # 比較兩個 Person 對象
>>> person0 == person1
False           

但是,當我們将這個新的對象添加到 set 對象 persons 時,兩個 Person 對象卻都包含在 set 中,這是我們不希望發生的。

因為根據設計,我們希望 set 對象存儲的自然人都不重複。通過比較 set 對象存儲的每一個 Person 對象,我們需要發現它們都是不相同的。

我将向你展示如何使自定義類 Person 更智能,以便它知道哪些自然人對象是相同的,哪些是不同的。

>>> # 優化 Person 類函數
>>> class Person:
...     # __init__ and __repr__ stay the same
...
...     def __hash__(self):
...         print("__hash__ is called")
...         return hash((self.name, self.ssn))
...
...     def __eq__(self, other):
...         print("__eq__ is called")
...         return (
...             self.__class__ == other.__class__ and 
...             self.name == other.name and
...             self.ssn == other.ssn
...         )
...
>>> # 建立兩個 Person 對象
>>> p0 = Person("Jennifer Richardson", 123456789)
>>> p1 = Person("Jennifer Richardson", 123456789)
>>> 
>>> # 建立一個集合,并包含這兩個對象
>>> ps = {p0, p1}
__hash__ is called
__hash__ is called
__eq__ is called
>>> ps
{***Name: Jennifer Richardson; SSN: 123456789***}
>>> 
>>> # 比較這兩個對象
>>> p0 == p1
__eq__ is called
True           

在上述代碼中,我們通過重寫 hash 和 eq 函數更新了自定義類 Person。

我們之前提到過,__hash__() 函數是用于計算對象的哈希值。而 __eq__() 函數用于比較對象與另一個對象是否相等,并且要求比較後相等的對象應該具有 相同的哈希值。

預設情況下,自定義類的執行個體是通過使用内置的 id() 函數擷取的辨別進行比較的(如果需要了解更多相關 id() 函數的内容請參閱 本文)。

通過更新後的實作,我們可以看到當我們試圖建立包含兩個相同 Person 對象的 set 對象時,__hash__() 會被調用進行判斷,然後集合對象中僅會保留具有唯一哈希值的對象。

另一件需要注意的事情是,當 Python 檢查 set 對象中的元素是否具有唯一哈希值時,它将通過調用 __eq__() 函數來確定這些對象不相等。

定制化:為了提供定制的哈希和比較政策,我們需要在我們的定制類中實作 hash 和 eq 函數。

總結

在本文中,我們回顧了 Python 裡面可哈希和哈希化的概念。

具體來說,通過解決這三個重要問題,我希望你能夠更好地了解 Python 中的哈希。當在适用的場景,你可以為自己的自定義類實作定制的哈希行為。

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/live

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK

原文釋出時間:2020-06-24

本文作者:cyril_lee

本文來自:“

掘金

”,了解相關資訊可以關注“掘金”