按如下方式构建字典时:
dict = { True: 'yes', 1: 'No'}
当我在交互式Python解释器中运行时,dict以这种方式表示:
dict = {True: 'No'}
我理解值True
和1
由于类型强制而相等,因为在比较数字类型时,缩小的类型被扩展为另一种类型(boolean是整数的子节点)。我从文档中了解到,当我们输入True == 1
时,Python会将True
转换为1
并对其进行比较。
我不明白为什么True
被选为关键而不是1
。
我遗失了什么?
答案 0 :(得分:41)
字典实现为哈希表,在此处添加键/值时有两个重要概念:哈希和相等。
要插入特定键/值,Python首先计算键的哈希值。此哈希值用于确定Python应首先尝试放置键/值的表的行。
如果哈希表的行为空,那么很好:新的键/值可以插入到字典中,填充空行。
但是,如果该行中已存在某些内容,则Python需要测试密钥是否相等。如果密钥相等(使用==
),则它们被认为是相同的密钥,Python只需更新该行上的相应值。
(如果键不相等,Python会查找表中的其他行,直到找到键或到达空行,但这与此问题无关。)
当您编写{True: 'yes', 1: 'No'}
时,您告诉Python创建一个新字典,然后用两个键/值对填充它。这些是从左到右处理的:True: 'yes'
然后是1: 'No'
。
我们hash(True)
等于1.键True
位于哈希表的第1行,字符串'yes'
是其值。
对于下一对,Python看到hash(1)
也是1,因此查看表的第1行。已经有了一些东西,所以现在Python检查密钥是否相等。我们1 == True
因此1
被视为与True
相同的密钥,因此其对应的值更改为字符串'No'
。
这会产生一个包含一个条目的字典:{True: 'No'}
。
如果你想了解CPython 3.5的内容,看看在表面 - Python级别下创建字典的内容,这里有更多细节。
Python代码{True: 'yes', 1: 'No'}
被解析为令牌并提供给编译器。鉴于语法,Python知道必须使用大括号内的值创建字典。将四个值加载到虚拟机堆栈(LOAD_CONST
)然后构建字典(BUILD_MAP
)的字节代码排队。
四个常数值按照它们看到的顺序被推到堆栈的顶部:
'No'
1
'yes'
True
然后使用参数BUILD_MAP
调用操作码2
(Python计算两个键/值对)。此操作码负责从堆栈中的项目实际创建字典。它看起来像this:
TARGET(BUILD_MAP) {
int i;
PyObject *map = _PyDict_NewPresized((Py_ssize_t)oparg);
if (map == NULL)
goto error;
for (i = oparg; i > 0; i--) {
int err;
PyObject *key = PEEK(2*i);
PyObject *value = PEEK(2*i - 1);
err = PyDict_SetItem(map, key, value);
if (err != 0) {
Py_DECREF(map);
goto error;
}
}
while (oparg--) {
Py_DECREF(POP());
Py_DECREF(POP());
}
PUSH(map);
DISPATCH();
}
这里的三个关键步骤如下:
使用_PyDict_NewPresized
创建空哈希表。小词典(只有几个项目,在这种情况下为2个)需要一个包含八行的表。
输入for
循环,从2开始(在本例中)并向下计数到0. PEEK(n)
是指向堆栈中第n个项目的宏。因此,在循环的第一次迭代中,我们将
PyObject *key = PEEK(2*2); /* item 4 down the stack */
PyObject *value = PEEK(2*2 - 1); /* item 3 down the stack */
这意味着*key
True
将*value
,'yes'
将在第一次循环中1
。在第二个,它将是'No'
和*key
。
PyDict_SetItem
将当前*value
和dictionary[key] = value
放入字典中。这与您编写def randomQuestion = Question.find("from Question order by rand()")
时调用的函数相同。它计算密钥的散列,以找出在哈希表中首先查找的位置,然后,如果需要,将密钥与该行上的任何现有密钥进行比较(如上所述)。答案 1 :(得分:31)
基本前提是 - True
和1
具有相同的哈希值并且彼此相等 - 这就是为什么它们不能成为哈希表中的单独键(技术上具有相同哈希值的不等对象) - 但是哈希冲突会降低性能。)
>>> True == 1
True
>>> hash(1)
1
>>> hash(True)
1
现在,让我们考虑一个字节码:
import dis
dis.dis("Dic = { True: 'yes', 1: 'No'}")
打印:
0 LOAD_CONST 0 (True)
3 LOAD_CONST 1 ('yes')
6 LOAD_CONST 2 (1)
9 LOAD_CONST 3 ('No')
12 BUILD_MAP 2
15 STORE_NAME 0 (Dic)
18 LOAD_CONST 4 (None)
21 RETURN_VALUE
基本上发生的事情是dict文字被标记化为键和值,并且它们被推送到堆栈。之后BUILD_MAP 2
将两对(键,值)转换为字典。
最有可能的堆栈数据顺序(这似乎是由dict文字中的键顺序决定的)和BUILD_MAP
的实现细节决定了生成的dict键和值。
似乎键值赋值是按照dict文字中定义的顺序完成的。
赋值行为与d[key] = value
操作相同,所以它基本上是:
key
不在dict中(通过相等):添加key
do dict value
存储在key
鉴于{True: 'yes', 1: 'No'}
:
{}
添加(True, 'yes')
(True, hash(True))
== (True, 1)
是dict中的新密钥1
至'yes'
添加(1, 'no')
1
在dict(1 == True
) - >字典中没有新密钥1
True
('no'
)的密钥值
结果:{True: 'No'}
正如我评论的那样,我不知道这是Python规范的保证,还是只是CPython实现定义的行为,它可能在其他解释器实现中有所不同。
答案 2 :(得分:21)
True
和1
是不同的对象,但它们都具有相同的值:
>>> True is 1
False
>>> True == 1
True
这类似于两个可能具有相同值的字符串,但存储在不同的内存位置:
>>> x = str(12345)
>>> y = str(12345)
>>> x == y
True
>>> x is y
False
第一个项目被添加到字典中;然后当添加另一个时,该值已作为键存在。因此密钥更新,密钥值是唯一的。
>>> {x: 1, y: 2}
{"12345": 2}
答案 3 :(得分:9)
如果密钥已经存在于字典中,则它不会仅覆盖关键值。
我认为写x = {True:"a", 1:"b"}
符合以下几点:
x = {}
x[True] = "a"
x[1] = "b"
当它到达x[1] = "b"
时,键True
已经在dict中,为什么要更改它?为什么不只是覆盖相关的值。
答案 4 :(得分:6)
排序似乎是原因。 代码示例:
>>> d = {True: 'true', 1: 'one'}
>>> d
{True: 'one'}
>>> d = {1: 'one', True: 'true'}
>>> d
{1: 'true'}
答案 5 :(得分:3)
这是一个含糊不清的陈述。
逻辑:d = { True: 'no', 1: 'yes'}
当python计算表达式时,它会按顺序执行,因此它等效于此。
d = dict()
d[True] = 'no'
d[1] = 'yes'
常量True是键,但它的计算结果为1,所以你只需设置键的值两次。