Python 基础系列 4 - 哈希值和可变性 Hash value and mutability

引言:

通过查阅一些资料阐述一下 Python 编程语言中 Hash value,mutability 的理解,以及通过 code 实例 来验证 __hash__和__eq__方法的深刻意义,对其它面向对象的语言也是相通的。更多 Python 基础序列文章,请参考 Python 基础系列大纲

内容提要:

  1. 哈希
    哈希的作用
    hash(object) 函数
    hashable,unhashable
  2. 怎么判断可变不可变 ?
  3. 怎样判断为同一对象呢?
  4. 有关__eq__和__ hash __方法
    默认的 __eq__和 __ hash 方法
    只重写__eq__方法
    只重写
    hash __方法
    重写__eq__和 __ hash __方法

哈希

哈希的作用

它是一个将大体量数据转化为很小数据的过程,甚至可以仅仅是一个数字,以便我们可以用在固定的时间复杂度下查询它,所以,哈希对高效的算法和数据结构很重要。

hash(object)

hash() 用于获取一个对象(字符串或者数值等)的哈希值。返回对象的哈希值。对象需要这两个方法才能使对象可哈希:__hash__和__eq__

什么是可哈希(hashable),不可哈希(unhashable)?

引用来自官网的一段话

An object is hashable if it has a hash value which never changes during its lifetime (it needs a hash() method), and can be compared to other objects (it needs an eq() method). Hashable objects which compare equal must have the same hash value.

也就是说一个对象可哈希,它的哈希值在整个生命周期是不可变的,而且它可以和其它对象进行比较,并且相等的对象其哈希值也是相同的

Hashability makes an object usable as a dictionary key and a set member, because these data structures use the hash value internally.

可哈希性使得一个个对象以字典的形式(key,value)使用,因为哈希值就可以区分对象。

Most of Python’s immutable built-in objects are hashable; mutable containers (such as lists or dictionaries) are not; immutable containers (such as tuples and frozensets) are only hashable if their elements are hashable. Objects which are instances of user-defined classes are hashable by default. They all compare unequal (except with themselves), and their hash value is derived from their id().

大多数 Python 内置的不可变对象是可哈希的,而可变容器类(例如 lists 或 dicrionaries,或sets)是不可哈希的,不可变容器类(例如 tuples 和 frozensets)可哈希当且仅当它包含的全部元素是可哈希的用户自定义的类对象默认是可哈希的,因为默认继承超类 Object 中__hash__和__eq__方法,hash 方法返回对象内存地址的引用的哈希值,eq 方法主要是比较两个对象是否指向同一个对象的引用。

str_object = 'python'
tuple_object = ('python')
tuple_immutable = (1,2,(3,4))
tuple_mutable = (1,2,[3,4])
list_object = ['python', 'java', 'r']
set_object = {'python'}
dict_object = {'language':'python'}
hash(str_object) # out: 4840421178446298472
hash(list_object[0]) # out: 4840421178446298472
hash(tuple_object) #out:4840421178446298472
hash(tuple_immutable)# output: 3794340727080330424

不可变容器类 tuple (1,2,[3,4]) 中含有可变元素 list[3,4],因此也是不可哈希的

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-25886bc07981> in <module>
----> 1 hash(tuple_mutable)

TypeError: unhashable type: 'list'
hash(dict_object)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-60aed2bdf4ce> in <module>
----> 1 hash(dict_object)
TypeError: unhashable type: 'dict'
hash(list_object)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-4974dcb26867> in <module>
----> 1 hash(list_object)

TypeError: unhashable type: 'list'
hash(set_object)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-18-0ca966ccf645> in <module>
----> 1 hash(set_object)

TypeError: unhashable type: 'set'

怎么判断可变不可变 ?

更改对象的值 看 id 是不是一样,id 一样的为可变,则不可哈希,改了值,id 变化,则为不可变,则可哈希

怎样判断为同一对象呢?

当两个实例被视为相等时,且它们必须返回相同的哈希值,认为两个实例是指向同一个引用,视为同一对象。如果集合中都存在哈希值,则认为该实例已存在于集合中,该实例被视为等于集合中具有相同哈希值的其中一个实例,并非代表具有相同哈希值唯一的实例。

有关__eq__和__ hash __方法

将通过 code 实例来阐述它们的奥秘。

默认的__eq__和__ hash __方法

没有 override 超类 Object 类的__eq__和__ hash __方法,p1, p2, p3, p4 是4个不同的实例

class Person:
    def __init__(self, name, identityId):
        self.name = name
        self.identityId = identityId

p1 = Person('Formal Name', 123456789)
p2 = Person('Formal Name', 123456789)
p3 = Person('Nick Name', 123456789)
p4 = Person('Nick Name', 123456789)
result = set()
result.add(p1)
result.add(p2)
result.add(p3)
result.add(p4)
print(len(result))  # output:4

只重写__eq__

仅 override 超类 Object 类的__eq__ ,但是默认超类 Object 类的__ hash __方法,也就是说对象的地址引用的 hash 值。
is 主要是判断 2 个变量是否引用的是同一个对象,如果是的话,则返回 true,否则返回 false。
== 用来判断两个对象的值是否相等(跟 Java 不同,Java 中 == 用来判断是否是同一个对象),
所以 p1, p2, p3 和 p4 尽管是 equal 的,但是 hash value 是不一样的,所以是不同的实例。而且 Person 是不可 hashable 的,以此也验证了上面的那句话,只有__hash__和__eq__一致实现才可以 hashable,即两个对象相等,其 hash 值也必须相等才可 hashable

class Person:
    def __init__(self, name, identityId):
        self.name = name
        self.identityId = identityId
        
    def __eq__(self, other):
        return isinstance(other, Person) and self.identityId == other.identityId

p1 = Person('Formal Name', 123456789)
p2 = Person('Formal Name', 123456789)
p3 = Person('Nick Name', 123456789)
p4 = Person('Nick Name', 123456789)
print(p1==p2) # output: True
print(p1==p3) # output: True
print(p1 is p2) # output: False
print(p1 is p3) # output: False
print(id(p1)) # output: 2239125918048
print(id(p2)) # output: 2239125918144
print(id(p3)) # output: 2239125776272
print(id(p4)) # output: 2239125775168
result = set()
result.add(p1)
result.add(p2)
result.add(p3)
result.add(p4)
print(len(result))    
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-43-f7b955e924d1> in <module>
     20 print(id(p4)) # output: 2239125775168
     21 result = set()
---> 22 result.add(p1)
     23 result.add(p2)
     24 result.add(p3)

TypeError: unhashable type: 'Person'

只重写__ hash __方法

override 超类 Object 的__ hash __方法,因此使用默认的 object 的__eq__是比较两个实例的地址引用是否相等,它只在obj1 is obj2 也是 true 时返回 true。换句话说,只有当两个实例完全相同的实例时,才认为它们相等。仅仅因为它们的散列匹配,并不能确定它们在一个集合中是唯一的。所以 p1, p2, p3, p4 哈希值是一样的,但是是不同对象。

class Person:
    def __init__(self, name, identityId):
        self.name = name
        self.identityId = identityId
        
    def __hash__(self):
        # use the hashcode of self.identityId since that is used
        # for equality checks as well
        return hash(self.identityId)

p1 = Person('Formal Name', 123456789)
p2 = Person('Formal Name', 123456789)
p3 = Person('Nick Name', 123456789)
p4 = Person('Nick Name', 123456789)
print(p1==p2) # output: False
print(p1==p3) # output: False
print(p1 is p2) # output: False
print(p1 is p3) # output: False
print(id(p1)) # output: 2239124972880
print(id(p2)) # output: 2239124973888
print(id(p3)) # output: 2239124973936
print(id(p4)) # output: 2239124973360
print(hash(p1))  #output:123456789
print(hash(p2))  #output:123456789
print(hash(p3))  #output:123456789
print(hash(p4))  #output:123456789
result = set()
result.add(p1)
result.add(p2)
result.add(p3)
result.add(p4)
print(len(result)) # output:4 

重写__ hash __ 和 __ eq __ 方法

override 超类 Object 的 __ hash __ 和 __ eq __ 方法, p1, p2, p3, p4 哈希值是一样的,而且是相等的,但是 id 是不一样的,因此还是指向不同的对象。但是为啥 set 还是视 p1,p2, p3, p4 为重复的对象呢?,有必要下一篇文章去研究一下set去重原理,这里先给个结论吧,哈哈!set 的去重是通过两个函数__hash__和__eq__结合实现的。当两个变量的哈希值不相同时,就认为这两个变量是不同的;当两个变量哈希值一样时,调用__eq__方法,当返回值为 True 时认为这两个变量是同一个,应该去除一个。返回 FALSE 时,不去重。

class Person:
    def __init__(self, name, identityId):
        self.name = name
        self.identityId = identityId
        
    def __hash__(self):
        # use the hashcode of self.identityId since that is used
        # for equality checks as well
        return hash(self.identityId)
    
    def __eq__(self, other):
        return isinstance(other, Person) and self.identityId == other.identityId

p1 = Person('Formal Name', 123456789)
p2 = Person('Formal Name', 123456789)
p3 = Person('Nick Name', 123456789)
p4 = Person('Nick Name', 123456789)
print(p1==p2) # output: True
print(p1==p3) # output: True
print(p1 is p2) # output: False
print(p1 is p3) # output: False
print(id(p1)) # output: 2239124972880
print(id(p2)) # output: 2239124973888
print(id(p3)) # output: 2239124973936
print(id(p4)) # output: 2239124973360
print(hash(p1))  #output:123456789
print(hash(p2))  #output:123456789
print(hash(p3))  #output:123456789
print(hash(p4))  #output:123456789
result = set()
result.add(p1)
result.add(p2)
result.add(p3)
result.add(p4)
print(len(result)) # output:1

以上就是本人对 hash,mutability 以及 hash,eq 方法的理解,也算是本人第一篇正式出道的博客文章吧,体会了深入研究的乐趣,哈哈,欢迎大家提出建议,一起学习,一起进步!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值