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个层次:
哈希值相等。
哈希值相同代表两个对象可能相等。如果不同,则两个对象不可能相等,也一定不可能是同一个对象。
比较结果相等。
这意味这哈希值已经是相等的了。这个比较运用的是“==”预算符,如果结果相等,那么两个对象可能是同一个
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个比较运算符,如:
x<y 调用 x._lt_(y)
x<=y 调用 x._le_(y)
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