关于python字典类型最疯狂的表达方式

[译]关于python字典类型最疯狂的表达方式

一个Python字典表达式谜题

这个子字典是从哪里来的

Umm..好吧,可以得到什么结论呢?

一篇来自 Dan Bader 的有趣的博文,一起来学习一下,如何去研究一个意外的Python现象。

一个Python字典表达式谜题

让我们探究一下下面这个晦涩的python字典表达式,以找出在python解释器的中未知的内部到底发生了什么。

有时候你会碰到一个很有深度的代码示例 --- 哪怕仅仅是一行代码,但是如果你能够有足够的思考,它可以教会你很多关于编程语言的知识。这样一个代码片段,就像是一个:一个在修行的过程中用来质疑和考验学生进步的问题或陈述。

译者注: ,大概就是修行的一种方式,详情见 wikipedia

我们将在本教程中讨论的小代码片段就是这样一个例子。乍看之下,它可能看起来像一个简单的词典表达式,但是仔细考虑时,通过cpython解释器,它会带你进行一次思维拓展的训练。

我从这个短短的一行代码中得到了一个启发,而且有一次在我参加的一个Python会议上,我还把作为我演讲的内容,并以此开始演讲。这也激发了我的python邮件列表成员间进行了一些积极的交流。

所以不用多说,就是这个代码片。花点时间思考一下下面的字典表达式,以及它计算后将得到的内容:

>>> {True:'yes',1:'no',1.0:'maybe'}

在这里,我先等会儿,大家思考一下...

5...

4...

3...

2...

1...

OK, 好了吗?

这是在cpython解释器交互界面中计算上述字典表达式时得到的结果:

>>> {True:'yes',1:'no',1.0:'maybe'}{True:'maybe'}

我承认,当我第一次看到这个结果时,我很惊讶。但是当你逐步研究其中发生的过程时,这一切都是有道理的。所以,让我们思考一下为什么我们得到这个 -我想说的是出乎意料- 的结果。

这个子字典是从哪里来的

当python处理我们的字典表达式时,它首先构造一个新的空字典对象;然后按照字典表达式给出的顺序赋键和值。

因此,当我们把它分解开的时候,我们的字典表达就相当于这个顺序的语句:

奇怪的是,Python认为在这个例子中使用的所有字典键是相等的:

>>>True==1==1.0True

OK,但在这里等一下。我确定你能够接受1.0 == 1,但实际情况是为什么 也会被认为等于1呢?我第一次看到这个字典表达式真的让我难住了。

在python文档中进行一些探索之后,我发现python将 作为了 类型的一个子类。这是在Python 2和Python 3的片段:

“The Boolean type is a subtype of the integer type, and Boolean values behave like the values 0 and 1, respectively, in almost all contexts, the exception being that when converted to a string, the strings ‘False’ or ‘True’ are returned, respectively.”

“布尔类型是整数类型的一个子类型,在几乎所有的上下文环境中布尔值的行为类似于值0和1,例外的是当转换为字符串时,会分别将字符串”False“或”True“返回。“( 原文 )

是的,这意味着你可以在编程时上使用 值作为Python中的列表或元组的索引:

>>> ['no', 'yes'][True]'yes'

但为了代码的可读性起见,您不应该类似这样的来使用布尔变量。(也请建议你的同事别这样做)

Anyway,让我们回过来看我们的字典表达式。

就python而言, , 和 都表示相同的字典键。当解释器计算字典表达式时,它会重复覆盖键 的值。这就解释了为什么最终产生的字典只包含一个键。

在我们继续之前,让我们再回顾一下原始字典表达式:

>>> {True:'yes',1:'no',1.0:'maybe'}{True:'maybe'}

这里为什么最终得到的结果是以 作为键呢?由于重复的赋值,最后不应该是把键也改为 了?经过对cpython解释器源代码的一些模式研究,我知道了,当一个新的值与字典的键关联的时候,python的字典不会更新键对象本身:

当然这个作为性能优化来说是有意义的 --- 如果键被认为是相同的,那么为什么要花时间更新原来的?在最开始的例子中,你也可以看到最初的 对象一直都没有被替换。因此,字典的字符串表示仍然打印为以 为键(而不是1或1.0)。

就目前我们所知而言,似乎看起来像是,结果中字典的值一直被覆盖,只是因为他们的键比较后相等。然而,事实上,这个结果也不单单是由 比较后相等就得出的。

等等,那哈希值呢?

python字典类型是由一个哈希表数据结构存储的。当我第一次看到这个令人惊讶的字典表达式时,我的直觉是这个结果与散列冲突有关。

哈希表中键的存储是根据每个键的哈希值的不同,包含在不同的“buckets”中。哈希值是指根据每个字典的键生成的一个固定长度的数字串,用来标识每个不同的键。( 哈希函数详情 )

这可以实现快速查找。在哈希表中搜索键对应的哈希数字串会快很多,而不是将完整的键对象与所有其他键进行比较,来检查互异性。

然而,通常计算哈希值的方式并不完美。并且,实际上会出现不同的两个或更多个键会生成相同的哈希值,并且它们最后会出现在相同的哈希表中。

如果两个键具有相同的哈希值,那就称为哈希冲突(hash collision),这是在哈希表插入和查找元素时需要处理的特殊情况。

基于这个结论,哈希值与我们从字典表达中得到的令人意外的结果有很大关系。所以让我们来看看键的哈希值是否也在这里起作用。

我定义了这样一个类来作为我们的测试工具:

这个类有两个特别之处。

第一,因为它的 魔术方法(译者注:双下划线开头双下划线结尾的是一些Python的“魔术”对象)总是返回true,所以这个类的所有实例和其他任何对象都会恒等:

第二,每个实例也将返回由内置函数生成的唯一哈希值值:

在CPython中, 函数返回的是一个对象在内存中的地址,并且是确定唯一的。

通过这个类,我们现在可以创建看上去与其他任何对象相同的对象,但它们都具有不同的哈希值。我们就可以通过这个来测试字典的键是否是基于它们的相等性比较结果来覆盖。

正如你所看到的,下面的一个例子中的键不会被覆盖,即使它们总是相等的:

下面,我们可以换个思路,如果返回相同的哈希值是不是就会让键被覆盖呢?

这个类的实例将相互比较一定不相等,但它们会拥有相同的哈希值1:

一起来看看python的字典在我们试图使用类的实例作为字典键时的结果:

如本例所示,“键被覆盖”的结果也并不是单独由哈希冲突引起的。

Umm..好吧,可以得到什么结论呢?

python字典类型是检查两个对象是否相等,并比较哈希值以确定两个密钥是否相同。让我们试着总结一下我们研究的结果:

字典表达式计算结果为 ,是因为键 , 和 都是相等的,并且它们都有相同的哈希值:

也许并不那么令人惊讶,这就是我们为何得到这个结果作为字典的最终结果的原因:

我们在这里涉及了很多方面内容,而这个特殊的python技巧起初可能有点令人难以置信 --- 所以我一开始就把它比作是 。

如果很难理解本文中的内容,请尝试在Python交互环境中逐个去检验一下代码示例。你会收获一些关于python深处知识。

注:转载请保留下面的内容

译文链接:http://vimiix.com/post/2017/12/28/python-mystery-dict-expression/

本文来自企鹅号 - Python热爱着媒体

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏工科狗和生物喵

【计算机本科补全计划】《C++ Primer》:类型转换

正文之前 学习,不如爆文?反正晚上也不会学习,某个家伙也对我爱理不理的!!!!(这才是最骚的吧),刚好欠了 C++ Primer太多烂账了。不如赶紧还了! 对了...

3118
来自专栏Crossin的编程教室

【Python 第18课】 bool类型转换

昨天最后留的几句关于bool类型的转换,其中有一行: bool('False') print一下结果,会发现是True。这是什么原因? 因为在pytho...

2484
来自专栏云霄雨霁

设计模式----工厂方法模式

1470
来自专栏诸葛青云的专栏

C语言夺命题十例,为啥C语言的总是这么恶趣味?

这些问题测试了C语言的高级知识,包括一些很少使用的特性。有效的C编程需要对诸如未定义的行为,递归和指针算术等概念有深入的理解,但是这些故意复杂的例子并不代表现实...

2073
来自专栏noteless

[一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念

说起来好像很啰嗦,但是如果有人告诉你 通过sin(x) 计算后, x的值被改变了,你不会觉得异常奇怪么

1212
来自专栏章鱼的慢慢技术路

《算法图解》第四章笔记与课后练习

1895
来自专栏编程

Python变量作用域

今天我们要说的是:变量的作用域 什么是作用域呢? 作用域就是某一个事件或者物体在某种情况下产生的特定的作用或效果(画外音:能不能说人话?) 好吧,那常老师来举个...

2156
来自专栏编程

Python数据类型之字符串第四季

各位小伙伴们 “黑一”快乐 本节课非常非常重要 请各位小伙伴 一定认真理解和学习 技术要点: 内建函数 函数的理解 如何使用一个函数 capitalize()函...

1850
来自专栏追不上乌龟的兔子

介绍一些Python str类的方法

上面的代码正确的返回了'0.333333',但是当x = 1 / 2时,由于小数只有一位,这个方案的结果就是'0.5'了,而不是预期中的'0.500000'。

1794
来自专栏take time, save time

初级程序员面试不靠谱指南(六)

五.很强很伟大的函数指针     我想看到这个标题中“函数指针”几个字之后,估计有一半人会选择关掉界面,因为我最开始学习C语言的时候这一章我曾无数次跳过,看到书...

34910

扫码关注云+社区

领取腾讯云代金券