我对python如何处理字典中的数据有疑问。假设我有一个简单的字典,其中数字作为键,数字作为值,如下所示:
a = { 5: 3, 20: 1, 1: 1, 5: 2, 100: 3, 11: 6,
14: 1, 15: 2, 16: 4, 17: 2, 25: 1, 19: 1 }
我想迭代这个字典并打印出密钥。每次我遍历字典(如下所示)时,它都会按顺序打印键。
这就是我想要它做的,但我想知道,为了我自己的知识,为什么会发生这种情况?每次都按递增顺序自动排序吗?正如你在上面的字典中看到的那样,键显然没有按顺序递增,但下面的输出按顺序打印它们。
我只是想获得一个清晰的理解,任何帮助将不胜感激。感谢
for i in a:
print i
输出:
1
5
11
14
15
16
17
19
20
25
100
答案 0 :(得分:3)
字典中的整数并不总是按键排序:
a = {2:0, 9:0}
print a.keys() # [9, 2]
Python词典是 Hash Tables ,它是一种特殊的数组,其中存储值的单元格的索引 派生在键上应用特殊功能(让我们称之为hash
功能)。
这样,如果要检索特定键的值,您可以再次计算键的hash
功能,将返回与之前相同的结果,获取存储值的索引。
hash
函数将 most 类型的数据转换为整数:
print hash(1) # 1
print hash('hello') # 840651671246116861
print hash((2,3)) # 3713082714463740756
每种类型都可以定义自己的方式来计算哈希,int
通常返回自己:
print hash(1) # 1
print hash(20) # 20
print hash(1000) # 1000
正如您所看到的那样,数字很快就会变大,我们不希望拥有一个包含 840651671246116861 单元格的数组,只是为了保存字符串hello
。
为了避免这个问题,我们可以使用n
元素创建一个数组,然后使用 hash 的剩余部分除以n
作为索引。 / p>
例如,如果我们想在 8 元素的数组中找到hello
的索引:
print hash('hello') % 8 # 5
因此,我们的字典会知道密钥hello
的值位于索引 8 。这就是字典的实现方式。
那么,为什么{2:0, 9:0}
没有按键排序?这是因为python词典是用 8 元素创建的,并且根据需要增长(更多关于here)。
让我们计算索引,将key = 2
和key = 9
的数据存储在n = 8
的字典中:
print hash(2) % 8 # 2 [hash(2) = 2 and 2 % 8 = 2]
print hash(9) % 8 # 1 [hash(9) = 9 and 9 % 8 = 1]
这意味着包含字典数据的数组将是:
| index | key | value |
|-------|-----|-------|
| 0 | | |
| 1 | 9 | 0 |
| 2 | 2 | 0 |
| 3 | | |
| 4 | | |
| 5 | | |
| 6 | | |
| 7 | | |
当迭代它时,顺序将是此表示中显示的顺序,因此9
将在2
之前。
您可以阅读有关here主题的更多信息。
答案 1 :(得分:2)
如果你想知道为什么Python总是把键按顺序排列......答案就是它没有。
如果您想知道为什么某些特定版本的Python的某些特定版本将您的特定键按排序顺序排列,那么唯一真正的答案就是源代码。
对于CPython(你可能正在使用的实现,如果你不知道你正在使用哪一个),源代码在Objects/dictobject.c
。它在3.4中发生了巨大变化,在此之前......我认为2.6 / 3.2,并且历史上还有一些其他不那么戏剧性的变化。因此,您必须确保查找您真正关心的版本。对于3.4,源位于http://hg.python.org/cpython/file/3.4/Objects/dictobject.c。它在C中,但有一些很好的评论解释它在做什么。如果你真的想要探索它,你甚至可以将它移植到Python并在pdb
下运行它。
除非你理解哈希表,否则读取代码可能不明显的一个关键问题是两个"巧合"在这里,不只是一个。首先,CPython的某些版本,当给出一个小的dict同时构建时,将按键的哈希值按顺序排列。其次,到目前为止,在所有版本的CPython中,小整数都是哈希,所以 - 几乎与其他任何类型不同 - "按哈希值排序"也意味着"按价值排序"。
答案 2 :(得分:1)
每当我循环浏览字典(如下所示)时,它就会打印出来 按键递增。
这只是偶然的。字典是无序对象集合,可通过键访问。
没有“自动排序”或任何其他类型。
只需考虑它一秒钟 - 设置自己的密钥的全部要点是能够通过它们获取,因此密钥具有“订单”并不重要 - 关键是你知道如何引用每个对象,因为你设置了它的键。这样可以非常快速地获取对象;因为它很容易找到。没有重复的键,因此内部字典可以以优化的方式存储以便快速访问。
将此与已订购的清单进行比较(并确保其订单)。在列表中,重点是通过列表中的引用来获取对象 - 即,相对于列表中其他对象的位置。因此,维持秩序是有道理的。
元组类似于列表,因为它们是有序的。元组和列表之间的区别之一是元组一旦设置,就无法更改(不能“增长”或“缩小”元组)。要修改元组,您必须创建另一个元组。因此,为了“增长”一个元组,将两个元组组合在一起以获得第三个不同的元组。最初的两个元组没有变化。
如果您想了解字典实施背后的技术细节以及它们如何在“引擎盖下”工作this question对所有各种信息都有很好的答案。
答案 3 :(得分:-2)