解析python中的__hash__方法与比较运算符

hash

python中有两个哈希库,分别是hashlib和zlib。对于复杂的值或很大的值使用这些哈希库可以提供很大的帮助。

set、frozenset和dict这些集合利用hash函数创建键,利用不可变对象的哈希值来高效查找集合中的对象。

在这里不可变性是重要的一点。因为字符串类型不可变,所以set、dict可以用字符串作为键;而list则不可以作为键。

object对象默认的hash是使用内部的id值生成的。hash()函数实际是调用的内部__hash__方法。

>> x = object()
>> hash(x)
Out[49]: 114650576571
>> id(x)
Out[50]: 1834409225136
>> id(x)/16
Out[52]: 114650576571.0

hash值与id有很强的关联性,并且是唯一的,即使在这种情况下:

>> y = object()
>> hash(y)
Out[54]: 114650576568
>> id(y)
Out[55]: 1834409225088
>> id(x)
Out[56]: 1834409225136
# 可以看到,同样是object的实例,hash是不同的

而一个有状态的、可变的对象内部的__hash__返回的是None, 并且是无法使用hash函数的。

>> hash([1,2])
Traceback (most recent call last):
  File "D:\Python\Python37\lib\site-packages\IPython\core\interactiveshell.py", line 3296, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-57-9ce67481a686>", line 1, in <module>
    hash([1,2])
TypeError: unhashable type: 'list'
等价比较

运算符 “==”,即_eq_ 方法也与hash有关联。默认的 __eq__方法是与 “is” 操作符等价的。

等价比较有3个层次:

  1. 哈希值相等。

    哈希值相同代表两个对象可能相等。如果不同,则两个对象不可能相等,也一定不可能是同一个对象。

  2. 比较结果相等。

    这意味这哈希值已经是相等的了。这个比较运用的是“==”预算符,如果结果相等,那么两个对象可能是同一个

  3. IDD相等。

    这意味着两个对象是同一个对象。他们的哈希值相等并且“==”比较的结果相等,这个比较用的是is运算符。

哈希比较是等价性比较的第一步,有相同哈希值的对象不一定相等。当创建集合和字典时,也一定会带来“==”比较的开销。

当我们编写一个可变对象的__hash__时,必须将其返回值设置为None,但是可以自定义__eq__方法。

>> [1,2] == [1,2]
Out[64]: True
>> [1,2].__hash__()
Traceback (most recent call last):
  File "D:\Python\Python37\lib\site-packages\IPython\core\interactiveshell.py", line 3296, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-66-78497992a8cb>", line 1, in <module>
    [1,2].__hash__()
TypeError: 'NoneType' object is not callable
>> '1'.__hash__()
Out[67]: 5538954498784384897

我们创建一个小明的类,来代表小明这个人。可以看到x和y是不等的,这是符合python逻辑的。但这并不是我们期望的,x和y明明代表的都是名字叫小明的人,他们应该相等。

class XiaoMing:
    pass
>> x = XiaoMing()
>> y = XiaoMing()
>> x == y
Out[72]: False

下面我们来优化一下这里面的逻辑,并且定义__eq__的的方法。

叫小明的人可能有很多,所以仅仅靠名字是不够的,但是身份证号是唯一的,所以我们为他添加一个“identity_id”属性,用这个属性来判断是不是同一个人。

因为人的属性是可变的,比如说年龄,甚至性别~ 所以这里返回None作为可变对象。

如果作为不可变对象,那么需要返回一个值,并且配合__slots__和__setattr__来创建一个不可变对象。

class XiaoMing:
    def __init__(self,identity_id):
        self.identity_id = identity_id

    def __eq__(self, other):
        return self.identity_id == other.identity_id
    
    __hash__ = None  # 人的属性是可变的,所以这里返回None作为可变对象
    # def __hash__(self):
    	# 假设为不可变的,那么需要返回一个值。
        # return self.identity_id**2 
        
>> x = XiaoMing('123456')
>> y = XiaoMing('123456')
>> z = XiaoMing('09876')
>> x == y
Out[77]: True
>> x == z
Out[78]: False
真假性

bool()函数实际上依赖于一个给定对象的__bool__方法,默认的返回True。

而对于一个人来说,如果有身份证号才是合法的,所以我们将 “小明” 的真假性委托个 “identity_id” 属性。

class XiaoMing:
    def __init__(self,identity_id):
        self.identity_id = identity_id

    def __eq__(self, other):
        return self.identity_id == other.identity_id
    
    def __bool__(self):
        return bool(self.identity_id)
大小比较

python中有6个比较运算符,如:

  1. x<y 调用 x._lt_(y)

  2. x<=y 调用 x._le_(y)

  3. x> y 调用 x._gt_(y)

    …省略…

对于一个人来说,大小的比较是对年龄的比较。而只有同为人类的小明之间才可以比较年龄。

class Person:
    pass


class XiaoMing(Person):
    def __init__(self, identity_id,age):
        self.identity_id = identity_id
        self.age = age
    def __eq__(self, other):
        return self.identity_id == other.identity_id

    def __bool__(self):
        return bool(self.identity_id)

    def __lt__(self, other):
        try: #可以使用try的方式来判断是否具有属性
            return self.age < other.age
        except AttributeError:
            return NotImplemented
    def __gt__(self, other):
        if isinstance(other,Person): # 也可以判断比较对象是不是人类
            return self.age > other.age
        else:
            return NotImplemented
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值