原标题:字典现在是有序的了,习惯它吧
在过去几周里,我曾多次听到人们讨论Python列表和字典之间的区别,其中提到的第一个区别是列表是有序的,而字典不是。
不过呢,现在已经不再是这样了。这里引用一下涉及上述话题的文档:
版本3.7的变化:确保字典顺序是插入顺序。这种行为是来自CPython 3.6版本的一个实现细节。
因此,如果您想讨论列表和字典基本的区别,您几乎只能指出字典的值是通过键进行访问的,键可以是任何不可变类型,而列表值是使用整数进行索引的。只有这点区别了:-)
为什么是这样?
普通散列表以一个伪随机顺序保存键和值,该顺序由从键计算而来的散列值决定。它同时也是稀疏的,在预先分配的数组中存在着大量未被占用的空位:
因为3.6版的CPython将键和值保存在一个单独的密集数组中,而散列表本身只保存其中的索引:
由于条目数组是按顺序填充的,所以它自然保证了顺序。
据我所知,这种变化的最初原因是希望通过共享具有相同键集的多个字典的哈希表来节省空间(例如,在Python中,这意味着相同类的实例)。我不知道这个有用的属性是如何从一个实现细节发展到一个有保证的行为的。
dict[int] 与 list
这是否意味着带有int键的字典与列表是相同的?它们还是有一些实际的区别。
一个明显的区别是API。在处理一个dict[int]时,你必须始终显式地提到索引,因为不存在没有参数的.append(value)或.pop这样的东西。坦率地说,我看不出有任何使用它的必要:-)
此外,字典要大一些,是按大小进行排序的:
令人惊讶的是,无论是在列表和dict[int]中插入新值,还是遍历它们,我都没有发现任何速度上的明显差异。我的直觉告诉我,这主要是因为int的散列值就是这个int本身,所以没有时间浪费在获取散列值上。
不过,集合不同!
是的,令我惊讶的是集合仍然是无序的:
以前,我一直以为集合基本上是围绕着实际字典的瘦包装器,它会对字典的键进行操作并忽略值。事实证明,它们有自己的实现。
评论: 33 (值得注意的: 3)
Mitya
有趣!这样一来,它将Python带到了与PHP同等的水平。
PHP(和Facebook Hack)是我所知道的唯一一种在标准容器工具包中有有序映射/字典的语言。
当我接触到Hack时,我最初的直觉是我并不会依赖于这个特性,但当我习惯了之后,我实际上开始欣赏它了。这好几次有助于我改进我的代码!
还有其他流行的语言有这种特性吗?
Alyssa
Mitya, Ruby具有有序字典也有一段时间了。
Steve (值得注意的评论)
关于这个主题,这里有一个很不错的演讲: https://youtu.be/p33CVV29OG8
Jonathan Hartley
Python有有序字典已经很长时间了(我认为在“containers.ordereddict”中)。上面所描述的变化,在Python 3.6(从2016年开始)中差不多就将“有序”行为扩展到一般字典了。
hoistbypetard
Mitya: C++的std::map是有序的。如果你想要一个无序的,你必须使用std::unordered_map。C#有OrderedDictionary。Java有SortedMap。
这是屡见不鲜的。
Ivan Sagalaev
@hoistbypetard据我所知,std::map是按键排序的,而不是按插入顺序。这可能在实践中并不重要,但仍然是值得知道的事情!
hoistbypetard
@Ivan Sagalaev:这和我的记忆相符。我忽略了“插入顺序”部分。可能是因为这对我来说从来都不重要,而一致的排序有时是很重要的(或者至少是有用的)。
顺便说一句,整体上分析的不错。
Ruslan Keba
欢迎回来评论!很高兴读到你的留言。
Ivan Sagalaev
@Ruslan Keba谢谢!:-)我不保证什么,去年我总共管理了2个帖子。我的队伍里有太多的草稿。
Eric
Brandon Rhodes在2017年的PyCon大会上发表了一个关于Python字典演变的精彩演讲:https://www.youtube.com/watch?v=66P5FMkWoVU。
漂亮的评论界面
对不起,只是进行测试,。这是一个很好的界面,标记格式很好。你要如何处理垃圾邮件呢(比如我的)?我希望我的博客也能有这样的东西。另外,评论是以数据库为后端的吗,还是你只是使用它来生成静态站点?
Austin
@hoistbypetard 在java中,你有LinkedHashMap来保持插入顺序。SortedMap用于按实际键本身来排序。
Mauro
我认为Python中的集合永远不会是有序的,因为
也就是说,它必须符合相等集合的数学定义:
如果两个集合具有相同的元素或集合的成员,则称集合A等于集合B。两个集合中元素的顺序无关紧要。
Andrej Shadura
Tcl默认也有有序字典。
Raindeer
一个应用是在解析JSON文件时,有序的字典是非常有用的。如果你想读取一个JSON文件,编辑一些内容,然后将其写回,有序的字典可以确保编辑后的文件中的对象属性与原始文件中的顺序相同。
Arkadi Klepatch (值得注意的评论)
@hoistbypetard @Ivan Sagalaev C++ std::map是一个树,而不是一个散列表。所以按键排序就自然而然了。完全不同的数据结构。
Ama Aje My Fren
Python3.7中可能没有.pop,但是有.popitem可以以有保证的后进先出顺序做相同的事情。
22
Java Map也是按插入顺序进行排序的。
Anonymous
Mitya, 在Tcl中,字典从一开始就是有序的。它们也是不可变的,我希望这是其他脚本语言的默认设置。
Somebody Somewhere
@hoistbypetard std::map是按键的值进行排序并以插入顺序保存的。例如,如果你插入以下键/值对(以从左到右的顺序): {3, "three"}, {1, "one"}, {2, "two"}。
- std::map 键顺序: { 1, 2, 3}
- Python 3.7 字典键顺序: {3, 1, 2}
Ivan Sagalaev
只是测试,对不起。这是一个很好的界面,标记格式很好。你如何处理垃圾邮件(比如我的)?我希望我的博客也能有这样的东西。另外,评论是数据库支持的吗,或者你是使用它来生成静态站点吗?
我通过手动批准所有评论来处理垃圾邮件:-)在某个地方有一个更复杂的系统,但它依赖于其他服务(Akismet),比我想要处理的东西更脆弱。所以我最终简化了所有的东西,因为我的博客最近流量不大。
当然,是的,它是一个自定义的数据库支持的软件。如果你想知道更多细节,请给我发邮件!
Alex Ambrioso
感谢你的好文章!
你写道:是的,令我惊讶的是,集合仍然是无序的!
我只是想指出,集合类型仍然是无序的,这并不奇怪。集合类型的行为基于这样一个数学概念:如果两个集合有相同的元素,那么它们就是相等的。我怀疑基本的集合类型特征永远不会改变。
Alex
我注意到Mauro也提出了同样的观点,我也做了一些评论。他引用集合的定义比我更清楚地说明了这一点。
Joseph Giralt
Mitya, Ruby从2013年开始就默认有有序散列了。
Babush
两个集合中元素的顺序是不相关的。
是的。让我们从中得出正确的结论。我们可以通过一个实现来对集合进行排序,同时也不会与数学定义产生冲突。也许你已经想到了集合的等式运算符。是的,该操作符的行为不应该改变:set(1,2,3)应该等于set(1,3,2),也应该等于set(3,1,2),等等。如果一种语言或库以某种顺序提供集合元素,那将不会改变这一规则。排序可以是固定的,也可以是随机的;不管怎样,我们在数学上仍然有一个集合。
Joseph Giralt
哎呀!从2007年开始,它就将该特性增加回来了。https://www.ruby-lang.org/en/news/2007/12/25/ruby-1-9-0-released/
re:fi.64 (值得注意的评论)
你可以阅读这两个帖子中的讨论:
Nandi
我认为无序的散列/映射/字典有安全隐患。我来自Perl,在那里,出于安全加强的目的,该语言走的是另一个方向,从有序到无序。这些安全注意事项是否发生了变化,或者它们在Python中是无关紧要的吗?
simcop2387
安全隐患与散列表中的冲突是紧密相连的。Python对事物排序的解决方案并没有改变这一点,而且它们在底层仍然具有与Perl 5使用的散列函数相同的随机性。
Alex
集合不需要任何排序。它们是collections,只不过集合没有顺序的概念。
Jonathan Hartley
Python中的集合永远不会是有序的
我认为,那些评论集合不能是有序的的人没有抓住要点。我们也可以对字典产生同样的争论,它只不过是每个键都附加了一个值的集合。
集合可以保留它们的所有现有行为,保留与数学集合的任何相似之处,除了在调用“pop”或对成员进行迭代时,它会按成员被插入的顺序来提供它们。
Eric
Java核心库中有https://docs.oracle.com/javase/8/docs/api/java/util/LinkedHashMap.html已经有很长一段时间了。
Evert
我对这个改变非常高兴,它对Python来说是一个游戏规则改变者。在过去,使用类来定义接口是很困难的,因为类中的元素顺序是未定义的。这使得为数据库编写OEM映射器(如SQLAlchemy)变得非常困难。他们必须使用复杂的元编程技巧来找出数据库表类中的元素是以什么顺序定义的。我曾经自己写过一个OEM作为练习,相信我,它并不漂亮。
随着3.6的发布,这个问题已经变得没有意义了。我爱它。新的类型声明系统是一个额外的大福利。我现在经常使用数据类来定义复杂的协议、解析器和序列化器的自动生成等,开销非常小。我爱它!
我热切地等待着3.6版本的发布,并且导入了数据类的一个预发布版,因为这些特性是一个游戏规则改变者。
Seb
嘿,真是个非常有趣的帖子,了解它很有用,但我用你的例子与计时器来验证了关于类似的插入/遍历时间的声明。我检查了它的3个操作:查找、插入和遍历。虽然对于遍历(迭代)来说列表可能更快,但是对于其他两个操作来说,字典和集合要快得多。我发现,对于在末尾插入元素(它们不需要移动内存,只需调用append)来说,列表可能会匹配相等的速度(运行时)。但是为了公平起见,我选择在可迭代对象的中间插入元素。请自行尝试以下代码,如果我在这里做错了什么,请告诉我:)
感谢你的考虑、关注和认可!
Seb
由于可读性不是很好,我在pastebin上重新发布了我的代码:https://pastebin.com/4aMY6eWP
英文原文:https://softwaremaniacs.org/blog/2020/02/05/dicts-ordered/en/ 译者:高山流水