Python 3.5在字典中比较它们时选择键的选择

时间:2016-05-11 13:26:36

标签: python dictionary

按如下方式构建字典时:

dict = { True: 'yes', 1: 'No'}

当我在交互式Python解释器中运行时,dict以这种方式表示:

dict = {True: 'No'}

我理解值True1由于类型强制而相等,因为在比较数字类型时,缩小的类型被扩展为另一种类型(boolean是整数的子节点)。我从文档中了解到,当我们输入True == 1时,Python会将True转换为1并对其进行比较。

我不明白为什么True被选为关键而不是1

我遗失了什么?

6 个答案:

答案 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();
    }
    

这里的三个关键步骤如下:

  1. 使用_PyDict_NewPresized创建空哈希表。小词典(只有几个项目,在这种情况下为2个)需要一个包含八行的表。

  2. 输入for循环,从2开始(在本例中)并向下计数到0. PEEK(n)是指向堆栈中第n个项目的宏。因此,在循环的第一次迭代中,我们将

  3. 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

      在每个循环中调用
    1. PyDict_SetItem将当前*valuedictionary[key] = value放入字典中。这与您编写def randomQuestion = Question.find("from Question order by rand()") 时调用的函数相同。它计算密钥的散列,以找出在哈希表中首先查找的位置,然后,如果需要,将密钥与该行上的任何现有密钥进行比较(如上所述)。

答案 1 :(得分:31)

基本前提是 - True1具有相同的哈希值并且彼此相等 - 这就是为什么它们不能成为哈希表中的单独键(技术上具有相同哈希值的不等对象) - 但是哈希冲突会降低性能。)

>>> 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'}

  1. {}
  2. 开始
  3. 添加(True, 'yes')

    1. True不在dict中 - > (True, hash(True)) == (True, 1)是dict中的新密钥
    2. 将密钥值更新为1'yes'
  4. 添加(1, 'no')

    1. 1在dict(1 == True) - >字典中没有新密钥
    2. 使用值1
    3. 更新等于True'no')的密钥值
  5. 结果:{True: 'No'}

    正如我评论的那样,我不知道这是Python规范的保证,还是只是CPython实现定义的行为,它可能在其他解释器实现中有所不同。

答案 2 :(得分:21)

True1是不同的对象,但它们都具有相同的值:

>>> 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,所以你只需设置键的值两次。