目录

一、说明

二、“可哈希”在Python中是什么意思?

2.1 什么是哈希(hashable)?

2.2 python的可哈希对象

2.3 实验和说明

2.4 什么是不可哈希(unhashable)?

三、更深的可哈希对象与不可哈希对象的理解

3.1 __hash__魔方函数

3.2 有关操作函数

3.3  自定义类型的对象是否可哈希的呢?

四、为什么字典 key 必须是不可变的(可哈希hashable)?

4.1 字典如何在 CPython 中实现?

4.2 字典 key 必须是不可变的(可哈希hashable)

五、总结


一、说明

在python中,每个数据都是对象的。可是对象与对象是不同的。可以分可哈希对象和不可哈希对象,其存储方式不同。本文针对这个话题展开。

二、“可哈希”在Python中是什么意思?

2.1 什么是哈希(hashable)?

解释python中可哈希对象的工作原理,必须首先了解术语“哈希”。

        散列(哈希)是计算机科学中的一个概念,用于创建高性能的伪随机访问数据结构,在该结构中要快速存储和访问大量数据。

例如,如果您有10,000个电话号码,并且想要将它们存储在一个数组中(这是一个顺序数据结构,可将数据存储在连续的内存位置中,并提供随机访问),但是您可能没有所需的连续数量内存位置。

您可以改为使用大小为100的数组,并使用哈希函数将一组值映射到相同的索引,并且这些值可以存储在链接列表中。这提供了类似于阵列的性能。至于这个原理细节可以参照我的博客:

【数据管理】谈谈哈希原理和散列表_无水先生的博客-CSDN博客

2.2 python的可哈希对象

          简要的说可哈希的数据类型,即不可变的数据结构(数字类型(int,float,bool)字符串str、元组tuple、自定义类的对象)。

       所有不可变类型列表:

int, float, decimal, complex, bool, string, tuple, range, frozenset, bytes

        所有可变类型列表:

list, dict, set, bytearray, user-defined classes

        为什么不可变数据类型是可哈希hashable的呢? 

对于不可变类型而言,不同的值意味着不同的内存,相同的值存储在相同的内存,如果将我们的不可变对象理解成哈希表中的Key,将内存理解为经过哈希运算的哈希值Value,这不正好满足哈希表的性质嘛。

如果对象的哈希值在其生命周期内始终不变(需要一个__hash__()方法),并且可以与其他对象进行比较(需要一个__eq__()or __cmp__()方法),则该对象是可哈希的。比较相等的可哈希对象必须具有相同的哈希值。

散列性使对象可用作字典键和set成员,因为这些数据结构在内部使用散列值。

Python的所有不可变内置对象都是可哈希的,而没有可变容器(例如列表或字典)是可哈希的。作为用户定义类实例的对象默认情况下可哈希化;它们都比较不相等,并且其哈希值是id()

2.3 实验和说明

用若干实验直接说明问题:

        实验1:常规变量理解,在变量不可变的情况,相同的值具备相同地址:

         理解什么是可变对象mutable与不可变对象inmutable。以及明白哈希值value的唯一性。

import numpy as npa1 = 100
a2 = 100
a3 = 100
a4 = 200
a5 = 200
print( id(a1),id(a2),id(a3),id(a4),id(a5))b1 = [1,2,3,4]
b2 = [1,2,3,4]
print(id(b1),id(b2))c1 = (1,2,3,4,5,6)
print(hash(a1),hash(a2))
print(hash("b1"))

C:\anaconda3\python.exe C:/Users/yan/PycharmProjects/speechDev/myroad/tan.py
140726921526112 140726921526112 140726921526112 140726921529312 140726921529312
2368347700936 2368347700744
100 100
-7628306277453902763

Process finished with exit code 0


        实验2:任何不可变的东西(定义以后不能修改、追加、删除,类似于C++的const关键词)都可以被散列。

除了要查找的哈希函数(如果有类)之外,还可以通过例如。dir(tuple)寻找__hash__方法(该方法返回布尔值,是或否),这里有一些例子

#x = hash(set([1,2])) #set unhashable
x = hash(frozenset([1,2])) #hashable
#x = hash(([1,2], [2,3])) #tuple of mutable objects, unhashable
x = hash((1,2,3)) #tuple of immutable objects, hashable
#x = hash()
#x = hash({1,2}) #list of mutable objects, unhashable
#x = hash([1,2,3]) #list of immutable objects, unhashable

        实验3:根据Python词汇表的理解,当您创建可哈希对象的实例时,还会根据实例的成员或值来计算不可更改的值。

例如,该值随后可以用作字典中的键,如下所示:

>>> tuple_a = (1,2,3)
>>> tuple_a.__hash__()
2528502973977326415
>>> tuple_b = (2,3,4)
>>> tuple_b.__hash__()
3789705017596477050
>>> tuple_c = (1,2,3)
>>> tuple_c.__hash__()
2528502973977326415
>>> id(a) == id(c)  # a and c same object?
False
>>> a.__hash__() == c.__hash__()  # a and c same value?
True
>>> dict_a = {}
>>> dict_a[tuple_a] = 'hiahia'
>>> dict_a[tuple_c]
'hiahia'

我们可以发现tuple_a和tuple_c的哈希值相同,因为它们具有相同的成员。当我们将tuple_a用作dict_a中的键时,我们可以发现dict_a [tuple_c]的值相同,这意味着,当它们用作dict中的键时,它们将返回相同的值,因为哈希值是相同。对于那些不可哈希的对象,方法哈希定义为“无”:

>>> type(dict.__hash__)
<class 'NoneType'>

我猜这个哈希值是在实例初始化时计算出来的,而不是以动态方式计算的,这就是为什么只有不可变对象才可以哈希的原因。希望这可以帮助。

        实验4:当我在Python 3中运行hash('Python')时,结果为5952713340227947791。不同版本的Python可以自由更改基础哈希函数,因此您可能会获得不同的值。重要的是,无论我现在多次运行hash('Python'),还是始终使用相同版本的Python获得相同的结果。

但是hash('Java')返回1753925553814008565。因此,如果要散列的对象发生了变化,结果也将发生变化。另一方面,如果我正在哈希的对象没有更改,则结果保持不变。

为什么这么重要?

例如,Python字典要求键是不可变的。即,键必须是不变的对象。字符串在Python中是不变的,其他基本类型(int,float,bool)也是如此。元组和冻结集也是不可变的。另一方面,列表不是不可变的(即,它们是可变的),因为您可以更改它们。同样,字典是易变的。

因此,当我们说某事是可哈希的时,我们表示它是不可变的。如果我尝试将可变类型传递给hash()函数,它将失败:

>>> hash('Python')
1687380313081734297
>>> hash('Java')
1753925553814008565
>>>
>>> hash([1, 2])
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> hash({1, 2})
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
>>> hash({1 : 2})
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
>>> hash(frozenset({1, 2}))
-1834016341293975159
>>> hash((1, 2))
3713081631934410656

查看英文原文

2.4 什么是不可哈希(unhashable)?

同理,不可哈希的数据类型,即可变的数据结构 (字典dict,列表list,集合set)

对于可变对象而言,比如一个列表,更改列表的值,但是对象的地址本身是不变的,也就是说不同的Key,映射到了相同的Value,这显然是不符合哈希值的特性的,即出现了哈希运算里面的冲突。如下:

a=[1,2,3]
print(id(a))
def func(p):p.append(4)return pb=func(a)
print(id(b))
'''2399750863880 是一样的哦2399750863880'''

   这种不能叫可哈希: 如果此时对a和b使用hash函数,则会出错,如下:

TypeError: unhashable type: 'list'

三、更深的可哈希对象与不可哈希对象的理解

3.1 __hash__魔方函数

并不是说哈希值就是它本身哈,一个对象的哈希值是什么取决于__hash__魔方函数

再看一个例子:

In [24]: x="ilove"
In [25]: y="i"+"love"
In [26]: z="iloveyou"In [27]: id(x),id(y),id(z)
Out[27]: (3122841661600, 3122841661600, 3122841929584) # x,y的id是一样的
In [28]: hash(x),hash(y),hash(z)Out[28]: (4255912298523051991, 4255912298523051991, -3820205610162521985) # x,y 的哈希值是一样的

3.2 有关操作函数

如果一个对象是可哈希的,那么在它的生存期内必须不可变(而且该对象需要一个哈希函数),而且可以和其他对象比较(需要比较方法).比较值相同的对象一定有相同的哈希值,即一个对象必须要包含有以下几个魔术方法:

  • __eq__():用于比较两个对象是否相等
  • __cmp__():用于比较两个对象的大小关系,它与__eq__只要有一个就可以了
  • __hash__():实际上就是哈希函数(散列函数),返回经过运算得到的哈希值

前面既然说了整数int是可哈希对象,不放我们看一下它具不具备这几个魔术方法:

In [51]: a=100
In [52]: dir(a)
Out[52]:[...'__eq__',...'__hash__',...]
  1. 我们发现他的确具有上面说的这几个魔术方法。列表是不可哈希的,我们看一下列表的魔术方法有哪一些:

In [54]: a=[1,2,3]In [55]: dir(a)Out[55]:[...'__eq__',...'__hash__',...']

我们发现一个问题,为什么可变对象list明明是不可哈希的,为什么也有着两个方法呢?

因为所有类型的基类object中实现了这两个魔术方法,但是并不是说有这两个方法就一定是可哈希的,关键是要如何实现__eq__()方法和__hash__()方法,list并没有实现,只是有这几个魔术方法而已,在实现的里面出发了上面的异常。我们可以看一下基类object的魔术方法,如下:

In [56]: dir(object)Out[56]:[...'__eq__',...'__hash__',...]

3.3  自定义类型的对象是否可哈希的呢?

看一下如下代码:

class Animal:def __init__(self, name):self.name=namedef eat(self):print("i love eat !")a=Animal("dog")print(hash(a)) # 83529594295

我们发现自定义的类的对象是可哈希的,虽然我们不知道这个哈希值是如何得到的,但是我们知道他的确是可哈希对象。

上面说了哈希值的计算实际上是通过__hash__魔术方法来实现的,我们不妨自定义一下类的魔术方法,如下:

class Animal:
def __init__(self, name):
self.name=name
def __hash__(self): # 自定义哈希函数
return 1000 # 注意哈希函数的返回值要是integer哦!
def eat(self):
print("i love eat !")
a=Animal("dog")
print(hash(a)) # 返回 1000

现在对于什么是python的可哈希对象和哈希函数如何实现应该有了比较清楚的了解了。

四、为什么字典 key 必须是不可变的(可哈希hashable)?

4.1 字典如何在 CPython 中实现?

CPython 的字典实现为可调整大小的哈希表。与 B-树相比,这在大多数情况下为查找(目前最常见的操作)提供了更好的性能,并且实现更简单。

字典的工作方式是使用 hash() 内置函数计算字典中存储的每个键的 hash 代码。hash 代码根据键和每个进程的种子而变化很大;例如,"Python" 的 hash 值为-539294296,而"python"(一个按位不同的字符串)的 hash 值为 1142331976。然后,hash 代码用于计算内部数组中将存储该值的位置。假设您存储的键都具有不同的 hash 值,这意味着字典需要恒定的时间 -- O(1),用 Big-O 表示法 -- 来检索一个键。

4.2 字典 key 必须是不可变的(可哈希hashable)

字典的哈希表实现使用从键值计算的哈希值来查找键。

(1)为什么可变对象不能作为键Key?

先来看一个简单的例子:

  1. d = {[1, 2]: '100'} # 构造一个字典,key是列表[1,2] ,是一个可变对象,是不可哈希的

  2. print(d[[1, 2]]) # 通过key去访问字典的值,触发keyerror异常

复制

为什么会触发异常呢?哈希按其地址(对象 id)列出的。在上面的两行代码中,第一行中的key是一个列表对象[1,2],第二行中要访问的的时候的那个key虽然也是[1,2],但是由于列表list是可变对象,虽然这两行的列表值一样,但是他们并不是同一个对象,它们的存储地址是不一样的,即id是不一样的,id不一样也导致了根据id计算得到的哈希值是不一样的,自然没有办法找到原来的那一个[1,2]的哈希值在哪里了。

注意:这需要能够很好的理解可变对象与不可变对象的内存分配才好哦!

(2)为什么不可变对象能作为键Key?

将上面例子中的列表[1,2]换成元组(1,2),先来看一个简单的例子:

  1. d = {(1, 2): '100'} # 构造一个字典,key是元组(1,2) ,是一个不可变对象,是可哈希的

  2. print(d[(1, 2)]) # 通过key去访问字典的值,打印 '100'

为什么这里不会触发异常呢?哈希按其地址(对象 id)列出的。在上面的两行代码中,第一行中的key是一个元组对象(1,2),第二行中要访问的的时候的那个key也是(1,2),但是由于元组tuple是不可变对象,那么这两行的元组值一样,所以它们的存储地址是一样的,即id是一样的,id一样也导致了根据id计算得到的哈希值是一样的,哈希值一样我自然可以搜索得到那个100在哪个地方了。

五、总结

1)凡是有常量性质的对象可以哈希化。

2)正是常量这种不变性质,可以将它们的内容(或值)映射到具体地址上(想想,如果内容可变是不是地址映射不唯一?)

3)可哈希下有一堆魔方方法可以有意识地调用。

【Python知识】 可哈希和不可哈希对象相关推荐

  1. 深入原理64式:26 python知识总结

    目标: 整理python知识,主要包含如下内容: 1.器(生成器.迭代器.装饰器等) 2.类(元类,多态,方法等) 3.进程池与线程池 4.协程 5.实现原理 6.算法 7.基础 8.python重要 ...

  2. 《Python知识手册》,V3.0版来了,2021年,走起!

    "种一棵树,最好的时间是十年前,其次是现在." 一.前言 大家好,今天给大家推荐我的一位好朋友,公众号「Python数据之道」号主 Lemon . 从 2017 年开始,Lemon ...

  3. python 哈希表_哈希表哪家强?编程语言找你来帮忙!

    点击关注上方"五分钟学算法", 设为"置顶或星标",第一时间送达干货. 转自编程技术宇宙 哈希表华山论剑 比特宇宙编程语言联合委员会准备举办一次大会,主题为哈希 ...

  4. python下载教程1001python下载教程-Python知识圈

    我们知道,苹果手机上的 APP 如果要做自动化测试的话,硬件上我们需要在 MAC 电脑,所以,我们需要在 Mac 电脑上搭建 APP 自动化环境.其中,Appium 是需要安装的,安装 Appium ...

  5. 太赞了!《Python知识手册》更新到v2.2版

    "种一棵树,最好的时间是十年前,其次是现在." 一.前言 大家好,今天给大家推荐我的一位好朋友,公众号「Python数据之道」号主 Lemon . 从 2017 年开始,Lemon ...

  6. 《Python知识手册》,高清pdf免费获取

    "种一棵树,最好的时间是十年前,其次是现在." 一.前言 大家好,今天给大家推荐我的一位好朋友,公众号「Python数据之道」号主 阳哥 . 从 2017 年开始,阳哥陆陆续续在公 ...

  7. 肝了我好久,《Python知识手册V1.0》出来了!!!

    最近这段时间,每天抽空整理了一份Python知识手册,当然也可以称为Python面试手册.以问答的形式去整理,包含基础篇.进阶篇.练习篇.爬虫篇.正则篇.Flask篇.Django篇.目前版本V1.0 ...

  8. python之路day9_亮仔的Python之路Day9——Python知识体系重组

    day9:2019-09-03 今日目的: 梳理python知识框架体系 温故而知新 最近总是在想 如何构建一个知识体系 我们经常接受碎片化的知识 但是缺乏积累,无法搭建属于自己的知识框架 我目前的解 ...

  9. Python介绍与特点(自学python知识整理)

    Python介绍与特点(自学python知识整理) Python 简介 Python 是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. Python 的设计: Python 是一种解释 ...

最新文章

  1. MyBatis入门学习教程-调用存储过程
  2. Eclipse在高分屏下图标过小的解决方法
  3. 全球及中国晶圆键合和解键合设备行业竞争格局分析及投资前景评估报告2021年版
  4. poj1769 线段树优化的dp
  5. mongo占用内存过大解决方案
  6. java spring web配置文件路径_java – spring配置文件和web.xml的确切位置在哪里?
  7. python安装pip之后 pip命令报错解决方法
  8. greensock下载_GreenSock动画平台初学者指南
  9. 树莓派4B安装WPS步骤及缺失字体问题
  10. Windows开启winrm
  11. C语言实现摄氏、华氏温度转换
  12. 数据挖掘:数据清洗——缺失值处理
  13. 药品集中采购系统mysql数据库数据_浙江省药品集中采购基础数据库资料申报说明及具体要求...
  14. memory parity error
  15. 用计算机弹九八k的乐谱,抖音计算器按出的音乐乐谱有哪些 抖音计算器乐谱汇总...
  16. 图片公式提取至文档word
  17. 深信服防火墙之安全评估与动态检测技术
  18. 【转】形容词、形容动词、名词的假定形
  19. AD 20联合KeyShot实现渲染
  20. nvue - 单行溢出隐藏

热门文章

  1. iPhone6将停产?库克是怎么想的
  2. 社交网络平台技术 考试卷
  3. 新闻稿件媒体代发宣传2336803766
  4. 什么是上升沿和下降沿?
  5. 【毕业设计】深度学习中文文本分类(新闻分类 情感分类 垃圾邮件分类)
  6. window系统如何恢复/查看swp文件
  7. android 乐器,手机轻松实现演奏 Android乐器软件推荐-520吉他网
  8. 服务器虚拟hub,什么是虚拟集线器HUB?
  9. 芋道源码的周八(2018.03.04)
  10. 六大设计原则_Andy_Issta_新浪博客