开头:读者的"玄学"字典谜题
上周,朋友发来了一段让他抓耳挠腮的代码:
python
>>> {True: 'foo', 1: 'bar', 1.0: 'baz'}
{True: 'baz'}
"我明明定义了布尔True
、整数1
、浮点数1.0
三个键,结果字典里只剩True
一个键,值还变成了最后一个'baz'
!这是啥情况?"
这条消息让我想起了当年自己初学 Python 时踩过的类似坑 ------ 看似 "不同" 的键,在字典里却被 "合并" 了。今天,咱们就用这个新例子当 "线索",一起拆解 Python 字典的底层逻辑。
第一步:字典的"盖楼"规则
要搞懂三个键为何只剩一个,得先明白字典是怎么"盖楼"的。
简单来说,字典的构建像搭乐高:先拼一个空架子(空字典),再按顺序往架子上装"键值对模块"。上面的代码等价于:
python
# 1. 拼空架子
my_dict = {}
# 2. 装第一个模块:键是True,值是'foo'
my_dict[True] = 'foo'
# 3. 装第二个模块:键是1,值是'bar'
my_dict[1] = 'bar'
# 4. 装第三个模块:键是1.0,值是'baz'
my_dict[1.0] = 'baz'
重点来了:字典的键是"喜新厌旧"的------如果后装的键和已存在的键"本质相同",就会覆盖旧值 。但问题是:True
(布尔)、1
(整数)、1.0
(浮点数)明明是三种不同的类型,怎么就"本质相同"了?
第二步:True
是"伪装的1"
要破解"键相同"的谜题,得从Python的类型关系说起。
在Python的世界里,布尔(bool
)是整数(int
)的"亲儿子"------官方文档明确写着:
"布尔类型是整数类型的子类型,
True
等价于整数1
,False
等价于整数0
。在大多数上下文中,布尔值的行为与对应的整数值一致。"
这意味着:
True == 1
→ 是真的(True
)1 == 1.0
→ 也是真的(浮点数1.0
的数值等于整数1
)- 所以
True == 1 == 1.0
→ 全等于!
用代码验证:
python
>>> True == 1
True
>>> 1 == 1.0
True
>>> True == 1 == 1.0
True
原来,在字典的"视角"里,这三个键根本就是"同一个人"!所以当依次插入True: 'foo'
、1: 'bar'
、1.0: 'baz'
时,后两次插入都是在"修改同一个键的值",最终只保留最后一次的'baz'
。
第三步:哈希值------字典的"身份证号"
但这里还有个疑问:就算三个键"数值相等",字典怎么确定它们是"同一个键"?难道只看==
吗?
这就要说到字典的底层"黑科技"------哈希表(Hash Table) 。字典能快速查找键值对,全靠哈希值:每个键会先通过__hash__
方法生成一个哈希值(类似"身份证号"),字典根据这个号码把键"扔"到对应的"抽屉"里;查找时,也先算哈希值,再去对应的抽屉里找。
关键规则是:只有当两个键的哈希值相同,且==
返回True
时,字典才会认为它们是同一个键。
验证这三个键的哈希值:
python
>>> hash(True)
1
>>> hash(1)
1
>>> hash(1.0)
1
三个键的哈希值都是1
,==
又全返回True
,字典自然把它们当同一个键。所以后插入的1: 'bar'
和1.0: 'baz'
,本质上都是在修改True
对应的值。
第四步:为什么键是True
而不是1
或1.0
?
最后一个疑问:三个键数值相等、哈希相同,为什么最终字典的键是True
,而不是后插入的1
或1.0
?
这涉及字典的"键保留规则":当多个键被视为相同时,字典会保留第一个插入的键对象。比如:
python
>>> temp = {1.0: 'test'}
>>> temp[True] = 'update'
>>> temp
{1.0: 'update'}
这里先插入1.0
,后插入True
(与1.0
相等),字典会保留第一个键1.0
,并更新它的值。回到原问题,原字典第一个插入的键是True
,所以最终键是True
,值被后续插入的'bar'
和'baz'
覆盖。
结论:三个"不同"键的终极真相
现在,我们可以彻底解开这个"变脸字典"的谜题了:
- 类型关系是根源 :Python中
bool
是int
的子类,True
等价于1
,1
又等价于1.0
(数值相等)。 - 哈希值是身份证 :三个键的哈希值都是
1
,字典通过"哈希值相同+==
为True"判定它们是同一个键。 - 先到先得保键形 :字典保留第一个插入的键对象(
True
),后续插入只更新值,不修改键。
所以,最终结果{True: 'baz'}
的本质是:三个键被字典视为同一对象,后插入的值覆盖了前值,而键保留了第一个插入的True
。
写在最后:这行代码教会我的事
这个看似"玄学"的字典表达式,其实藏着Python最核心的设计逻辑:
- 布尔类型的"隐藏身份"(
int
子类) - 字典的哈希表底层逻辑(哈希值+相等性双重校验)
- 键值对的插入顺序对结果的影响
下次遇到类似"反直觉"的代码时,别急着怀疑语言bug------打开Python解释器,用==
和hash()
验证一下,你会发现Python的底层逻辑远比想象中严谨。
毕竟,Python的"奇怪",往往藏着最精妙的设计。