Python中使用__hash__和__eq__方法的問題
Python使用__hash__和__eq__的問題
- 代碼版本3.6.3
- 文檔版本:3.6.6
object.__hash__(self)
Called by built-in function hash() and for operations on members of hashed collections including set, frozenset, and dict.
__hash__()方法會(huì)被上述四種情況調(diào)用。
If a class does not define an __eq__() method it should not define a __hash__()operation either; if it defines __eq__() but not __hash__(), its instances will not be usable as items in hashable collections. If a class defines mutable objects and implements an __eq__() method, it should not implement __hash__(), since the implementation of hashable collections requires that a key’s hash value is immutable (if the object’s hash value changes, it will be in the wrong hash bucket).
如果自定義類沒定義__eq__()方法,那也不應(yīng)該定義__hash__()方法。
如果定義了__eq__()方法沒有定義__hash__()方法,那么它無法作為哈希集合的元素使用(這個(gè)hashable collections值得是set、frozenset和dict)。
這其實(shí)是因?yàn)橹貙慱_eq__()方法后會(huì)默認(rèn)把__hash__賦為None(文檔后面有說),像list一樣。
class A: ? ? def __eq__(self, other): ? ? ? ? pass ? ? a = A() ? print(a.__hash__) ?# None hash(a) ? ? # TypeError: unhashable type: 'A'
還專門說明:如果定義可變對(duì)象的類實(shí)現(xiàn)了__eq__()方法,就不要再實(shí)現(xiàn)__hash__()方法,否則這個(gè)對(duì)象的hash值發(fā)生變化會(huì)導(dǎo)致被放在錯(cuò)誤的哈希桶中。這個(gè)可以用字典試一下,你的鍵值不在是一一對(duì)應(yīng)的,只要能讓這兩個(gè)方法返回一致的對(duì)象都能改動(dòng)那個(gè)本不屬于自己的值,這篇文章的第五個(gè)例子就是這種情況。
User-defined classes have __eq__() and __hash__() methods by default; with them, all objects compare unequal (except with themselves) and x.__hash__() returns an appropriate value such that x == y implies both that x is y and hash(x) == hash(y).
用戶定義的類默認(rèn)都有__eq__()和__hash__()方法,這是從object繼承的,如果你不重寫任何一個(gè),那么對(duì)這個(gè)類的兩個(gè)實(shí)例x,y來說,x is y ,x == y , hash(x) == hash(y)會(huì)同時(shí)成立/不成立,即只有在x就是y的時(shí)候成立。
A class that overrides __eq__() and does not define __hash__() will have its __hash__()implicitly set to None. When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError when a program attempts to retrieve their hash value, and will also be correctly identified as unhashable when checking isinstance(obj, collections.abc.Hashable).
重寫了__eq__()方法的類會(huì)隱式的把__hash__賦為None。當(dāng)獲取實(shí)例的哈希值即用到了__hash__()方法時(shí)(只有上文提到的四種情況會(huì)用到這個(gè)方法)就會(huì)拋出TypeError錯(cuò)誤,上文例子演示過了。
并且isinstance判斷類型也能正確判斷。
# 直接安裝不成功 ?pip install collections2 才行 # collections2==0.3.0 ?A set of improved data types inspired by the standard library's collections module. import collections ? ? class A: ? ? def __eq__(self, other): ? ? ? ? pass ? ? class B: ? ? pass ? ? a = A() b = B() ? print(isinstance(a, collections.abc.Hashable)) ?# False print(isinstance(b, collections.abc.Hashable)) ?# True
If a class that overrides __eq__() needs to retain the implementation of __hash__() from a parent class, the interpreter must be told this explicitly by setting __hash__ =<ParentClass>.__hash__.
If a class that does not override __eq__() wishes to suppress hash support, it should include __hash__ = None in the class definition. A class which defines its own __hash__() that explicitly raises a TypeError would be incorrectly identified as hashable by an isinstance(obj, collections.abc.Hashable) call.
如果一個(gè)類重寫了__eq__()方法還需要能使用父類的__hash__()方法(上文已說默認(rèn)情況下是被賦值為None了),那就需要明確的說明一下:例class A;如果一個(gè)類沒有重寫__eq__()方法而又需要讓__hash__()失效,那就要明確的賦值為None,像list、set等的源碼那樣。
如果你重寫了一個(gè)會(huì)拋出異常的__hash__()方法,雖然使用時(shí)會(huì)拋出異常,但是類型判斷還是會(huì)判斷為是可哈希的,這是要注意的:例class B。
import collections ? ? class A: ? ? def __eq__(self, other): ? ? ? ? pass ? ? __hash__ = object.__hash__ ? ? class B: ? ? def __hash__(self): ? ? ? ? raise TypeError('There is an error!') ? ? a = A() b = B() ? print(isinstance(a, collections.abc.Hashable)) print(isinstance(b, collections.abc.Hashable)) hash(b) ? ? # 結(jié)果: # True # True # ...line 12, in __hash__... # TypeError: There is an error!
Python類中特殊方法__eq__和__hash__關(guān)系
class Point(object): def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return repr((self.id, self.x, self.y)) def __eq__(self, other): return self.x == other.y and self.y == self.y def __hash__(self): return hash((self.x, self.y))
上面定義了一個(gè)二維點(diǎn)的類其中__repr__主要用來以一個(gè)字符串表示該類的實(shí)例,例如Point(1,2),在調(diào)試時(shí)打印該點(diǎn)會(huì)獲得字符串(1,2)。
當(dāng)對(duì)兩個(gè)點(diǎn)的實(shí)例進(jìn)行值的比較時(shí),比如p1=Point(1,1) p2=Point(1,2),判斷p1==p2時(shí)__eq__()會(huì)被調(diào)用,用以判斷兩個(gè)實(shí)例是否相等。在上述代碼中定義了只要x和y的坐標(biāo)相同,兩個(gè)點(diǎn)相等。需要注意,__eq__()對(duì)is不生效,==是比較的值,而is比較的是引用,也就是內(nèi)存地址。舉個(gè)例子,p1=Point(1,1) p2=Point(1,1),p1==p2為True,p1 is p2為False,只有p1 is p1為True。
在Python中對(duì)象分為可哈希對(duì)象和不可哈希對(duì)象,可哈希對(duì)象如字符串、數(shù)字、自定義的類、frozenset、元組,被稱作不可變對(duì)象,不可哈希對(duì)象如字典、列表、集合,被稱作可變對(duì)象。這里的不可變不是對(duì)象的值不可變,而是指對(duì)象創(chuàng)建后其hash值在其生命周期內(nèi)不會(huì)改變。用函數(shù)hash()取可哈希對(duì)象的hash值,只要是同一對(duì)象其hash值不會(huì)改變;而對(duì)不可哈希對(duì)象取hash值,例如對(duì)列表取hash值,會(huì)報(bào)錯(cuò),返回TypeError: unhashable type: 'list'??晒?duì)象因其hash值不變可以用作字典的key,而不可哈希對(duì)象則不行。
當(dāng)需要對(duì)類的一個(gè)實(shí)例取其hash值時(shí),會(huì)調(diào)用__hash__()。一般來說,會(huì)把實(shí)例的所有屬性打包成元組,返回其hash值,從而實(shí)現(xiàn)自定義__hash__()。在用set()去重時(shí)就是對(duì)比hash值是否一樣,如果兩個(gè)對(duì)象hash值一樣代表重復(fù)。
用戶定義的類默認(rèn)帶有__eq__()和 __hash__()方法;使用它們與任何對(duì)象(自己除外)比較必定不相等,并且 x.__hash__()會(huì)返回一個(gè)恰當(dāng)?shù)闹狄源_保 x == y 同時(shí)意味著 x is y且 hash(x) == hash(y)。
如果一個(gè)類沒有定義__eq__()方法,那么也不應(yīng)該定義 __hash__()操作;如果它定義了__eq__()但沒有定義 __hash__(),那么__hash__()會(huì)被隱式地設(shè)為None,這個(gè)類就變成了不可哈希對(duì)象。如果一個(gè)類定義了可變對(duì)象并實(shí)現(xiàn)了 __eq__()方法,則不應(yīng)該實(shí)現(xiàn)__hash__(),因?yàn)榭晒<膶?shí)現(xiàn)要求鍵的哈希集是不可變的。例如,Point類中添加一個(gè)屬性li是一個(gè)列表,由于列表不可哈希所以強(qiáng)行放入包含屬性的元組中并返回其哈希值會(huì)報(bào)錯(cuò)。
如果使用默認(rèn)的__hash__()則不論如何改變一個(gè)實(shí)例的值其hash值都不變;反之,使用本文這種自定義的__hash__()方法,實(shí)例的值改變后,hash值就會(huì)改變。因此,自定義__hash__()方法的類的實(shí)例不應(yīng)該作為字典的key(強(qiáng)行作為key不會(huì)報(bào)錯(cuò),但是改變實(shí)例的屬性值會(huì)導(dǎo)致找不到key對(duì)應(yīng)的value),key的哈希值必須唯一不可變,key的hash值改變會(huì)導(dǎo)致找不到key對(duì)應(yīng)的value。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
在Python中使用cookielib和urllib2配合PyQuery抓取網(wǎng)頁信息
這篇文章主要介紹了在Python中使用cookielib和rllib2配合PyQuery抓取網(wǎng)頁信息的教程,主要是利用PyQuery解析HTML來實(shí)現(xiàn),需要的朋友可以參考下2015-04-04Python?OpenCV中cv2.minAreaRect實(shí)例解析
minAreaRect的主要作用是獲取一個(gè)多邊形(就是有很多個(gè)點(diǎn)組成的一個(gè)圖形)的最小旋轉(zhuǎn)矩形(旋轉(zhuǎn)矩形就是我們平常見到的水平框帶了角度),這篇文章主要給大家介紹了關(guān)于Python?OpenCV中cv2.minAreaRect的相關(guān)資料,需要的朋友可以參考下2022-11-11詳解超星腳本出現(xiàn)亂碼問題的解決方法(Python)
超星助手是一款為孩子們提供學(xué)習(xí)的軟件,支持用戶們后臺(tái)運(yùn)行多開等,還可以簽到,查題等多功能,下面這篇文章主要給大家介紹了關(guān)于超星腳本出現(xiàn)亂碼問題的解決方法,需要的朋友可以參考下2022-05-05如何用Python對(duì)數(shù)學(xué)函數(shù)進(jìn)行求值、求偏導(dǎo)
這篇文章主要介紹了如何用Python對(duì)數(shù)學(xué)函數(shù)進(jìn)行求值、求偏導(dǎo)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05python操作Excel神器openpyxl看這一篇就夠了
Python使用openpyxl讀寫excel文件這是一個(gè)第三方庫,可以處理xlsx格式的Excel文件,下面這篇文章主要給大家介紹了關(guān)于python操作Excel神器openpyxl的相關(guān)資料,需要的朋友可以參考下2023-04-04Python符號(hào)計(jì)算之實(shí)現(xiàn)函數(shù)極限的方法
這篇文章主要介紹了Python符號(hào)計(jì)算之實(shí)現(xiàn)函數(shù)極限的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07python之np.argmax()及對(duì)axis=0或者1的理解
這篇文章主要介紹了python之np.argmax()及對(duì)axis=0或者1的理解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06