Python 字典的一个奇异现象

简介: Python 字典的一个奇异现象

字典(dict)类型在 Python 基础数据类型中有着举足轻重的地位,日常编码中几乎离不开字典的使用。不过字典中有一个看似非常奇怪的现象,常被人忽略。本篇文章就来探索这一奇异现象。

请用 5 秒钟思考一下,下面这个字典表达式会输出什么?

>>> {True: 'True', 1: '1', 1.0: '1.0'}

将以上字典表达式复制到 Python 控制台中执行,将会得到如下结果:

{True: '1.0'}

初次见到这个结果时我是非常吃惊的,what ?这看起来似乎并不符合常理,但当我们一步一步分析出背后的原因之后,就不会对这个结果感到奇怪了。

我们可以将上面的字典表达式拆解为多个步骤来分析。

>>> d = dict()
>>> d[True] = 'True'
>>> d[1] = '1'
>>> d[1.0] = '1.0'
>>> d
{True: '1.0'}

首先,先创建一个空的字典对象,然后依次给字典赋值,最终得到字典结果为 {True: '1.0'}

从以上代码的执行步骤和结果,我们可以分析出,在 Python 中字典会将作为键的 True11.0 认为是 相等 的。我们可以在 Python 控制台得到验证。

>>> True == 1 == 1.0
True

果然,这三个对象对 python 来说是 相等 的。实际上,在 Python 中,bool 类型继承自 int 类型,所以这三个对象 相等 也是合理的。

Python 提供了 issubclass 函数可以判断一个类型是否是另一个类型的子类,使用这个函数来验证 bool 类型是 int 类型的子类。

>>> issubclass(bool, int)
True

还可以使用 isinstance 函数来判断一个对象是否是一个类型的实例,由于 Truebool 类型的实例,那么 True 自然也是 int 类型的实例对象。

>>> isinstance(True, int)
True

我们证实了 True11.0相等 的对象。但这还不足以说明问题,在 Python 中两个对象的值 相等 并不能说明它们放到字典中时会变为同一个键。这点通过稍后的示例就会明白。

在 Python 中,一个对象是否可以作为字典的键是有要求的,我们知道只有不可变类型才能作为字典的键。实际上 Python 判断一个对象是否可以作为字典的键,其实是判断这个对象是否为 可哈希hashable)对象。

Python 在 官网文档 中对 可哈希 对象进行了说明。大概意思是说:如果一个对象的 hash 值在其生命周期内不会改变(需要实现 __hash__() 方法),并且这个对象可以与其他对象进行比较(需要实现 __eq__() 方法),则该对象是 可哈希 的。由于 Python 内置的常见不可变类型都实现了 __hash__() 方法 和 __eq__() 方法,所以它们都是 可哈希 的。

这里还要强调一点:如果两个 可哈希 对象的值是相等的,那么它们的 hash 值也必然是相等的。这是 Python 的规范,我们自定义的类型也要遵循这个规范。

说的直白一些,Python 字典的键是不可重复的,而 Python 在操作字典的键时,如果两个对象的 hash 值相同,并且这两个对象的值也 相等,那么这两个对象会被当作同一个键。

Python 提供了 hash 函数可以获得一个对象的 hash 值,它会自动调用对象的 __hash__() 方法。至于判断两个对象的值是否相等,实际上就是使用 == 运算符,它会自动调用对象的 __eq__() 方法。

知道了有关字典的键的特性,接下来我们自己实现一个类,通过三个示例分别对三种不同的情况进行探索,从而通过实际的代码来验证字典的键的特性。

(以下示例代码中定义的 __init__ 方法和 __repr__ 方法只用于辅助观察,并不会对结果产生影响。)

示例一:

>>> class A(object):
...     def __init__(self, name):
...             self.name = name
...     def __repr__(self):
...             return self.name
...     def __eq__(self, other):
...             return True
...     def __hash__(self):
...             return id(self)
...
>>> a1 = A('a1')
>>> a2 = A('a2')
>>> a1 == a2
True
>>> hash(a1), hash(a2)
(4332959632, 4332959248)
>>> d = {a1: 1, a2: 2}
>>> d
{a1: 1, a2: 2}

由示例一可知,a1a2 两个对象的值相等,但 hash 值不同,最终得到的字典并不会将这两个对象看作同一个键。

示例二:

>>> class A(object):
...     def __init__(self, name):
...             self.name = name
...     def __repr__(self):
...             return self.name
...     def __eq__(self, other):
...             return False
...     def __hash__(self):
...             return 1
...
>>> a1 = A('a1')
>>> a2 = A('a2')
>>> a1 == a2
False
>>> hash(a1), hash(a2)
(1, 1)
>>> d = {a1: 1, a2: 2}
>>> d
{a1: 1, a2: 2}

由示例二可知,a1a2 两个对象的 hash 值相同,但值不相等,最终得到的字典也不会将这两个对象看作同一个键。

示例三:

>>> class A(object):
...     def __init__(self, name):
...             self.name = name
...     def __repr__(self):
...             return self.name
...     def __eq__(self, other):
...             return True
...     def __hash__(self):
...             return 1
...
>>> a1 = A('a1')
>>> a2 = A('a2')
>>> a1 == a2
True
>>> hash(a1), hash(a2)
(1, 1)
>>> d = {a1: 1, a2: 2}
>>> d
{a1: 2}

由示例三可知,a1a2 两个对象的 hash 值相同,并且值也相等,最终得到的字典会将这两个对象看作同一个键。

通过以上三个示例代码的演示,想必不用我多说,你一定已经猜测到了,实际上在 Python 中,True11.0 这三个对象的 hash 值也是相同的。

>>> hash(True), hash(1), hash(1.0)
(1, 1, 1)

至此,关于 Python 字典中的 奇异 现象也就解释通了,下次再见到同样的问题就不会觉得奇怪了。

相关文章
|
6天前
|
JSON 监控 安全
深入理解 Python 的 eval() 函数与空全局字典 {}
`eval()` 函数在 Python 中能将字符串解析为代码并执行,但伴随安全风险,尤其在处理不受信任的输入时。传递空全局字典 {} 可限制其访问内置对象,但仍存隐患。建议通过限制函数和变量、使用沙箱环境、避免复杂表达式、验证输入等提高安全性。更推荐使用 `ast.literal_eval()`、自定义解析器或 JSON 解析等替代方案,以确保代码安全性和可靠性。
19 2
|
2月前
|
XML JSON API
如何使用Python将字典转换为XML
本文介绍了如何使用Python中的`xml.etree.ElementTree`库将字典数据结构转换为XML格式。通过定义递归函数处理字典到XML元素的转换,生成符合标准的XML文档,适用于与旧系统交互或需支持复杂文档结构的场景。示例代码展示了将一个简单字典转换为XML的具体实现过程。
20 1
|
4月前
|
存储 JSON 索引
一文让你彻底搞懂 Python 字典是怎么实现的
一文让你彻底搞懂 Python 字典是怎么实现的
71 13
|
3月前
|
存储 Java Serverless
【Python】字典
【Python】字典
40 1
|
4月前
|
存储 数据安全/隐私保护 Python
Python常用数据结构——字典的应用
Python常用数据结构——字典的应用
48 2
|
4月前
|
关系型数据库 MySQL 数据库
Python MySQL查询返回字典类型数据的方法
通过使用 `mysql-connector-python`库并选择 `MySQLCursorDict`作为游标类型,您可以轻松地将MySQL查询结果以字典类型返回。这种方式提高了代码的可读性,使得数据操作更加直观和方便。上述步骤和示例代码展示了如何实现这一功能,希望对您的项目开发有所帮助。
183 4
|
4月前
|
Python
Python 字典删除下标前两个
Python 字典删除下标前两个
26 1
|
3月前
|
存储 安全 Serverless
Python学习四:流程控制语句(if-else、while、for),高级数据类型(字符串、列表、元组、字典)的操作
这篇文章主要介绍了Python中的流程控制语句(包括if-else、while、for循环)和高级数据类型(字符串、列表、元组、字典)的操作。
50 0
|
3月前
|
存储 自然语言处理 数据库
Python字典操作实现文章敏感词检索
Python字典操作实现文章敏感词检索
39 0
|
3月前
|
存储 JSON 数据处理
分析、总结Python使用列表、元组、字典的场景
分析、总结Python使用列表、元组、字典的场景
38 0