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

Ikr*_*a_5 60 python dictionary

构建字典时如下:

dict = { True: 'yes', 1: 'No'}
Run Code Online (Sandbox Code Playgroud)

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

dict = {True: 'No'}
Run Code Online (Sandbox Code Playgroud)

据我所知,由于类型强制,值True1相等,因为在比较数字类型时,缩小的类型被扩展为另一种类型(布尔值是整数的子节点).因此,正如我从文档的理解,当我们进入True == 1的Python转换True1并对它们进行比较.

我不明白为什么True选择它作为关键而不是1.

我错过了什么?

Ale*_*ley 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
    
    Run Code Online (Sandbox Code Playgroud)
  • BUILD_MAP然后使用参数调用操作码2(Python计算两个键/值对).此操作码负责从堆栈中的项目实际创建字典.它看起来像这样:

    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();
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

  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 */
Run Code Online (Sandbox Code Playgroud)

这意味着,*key将是True*value'yes'通过第一循环.在第二个它将是1'No'.

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

  • @MartijnPieters`float("nan")== float("nan") - > False`然后`hash(float("nan"))== hash(float("nan")) - > True (2认同)

Łuk*_*ski 31

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

>>> True == 1
True
>>> hash(1)
1
>>> hash(True)
1
Run Code Online (Sandbox Code Playgroud)

现在,让我们考虑一个字节码:

import dis
dis.dis("Dic = { True: 'yes', 1: 'No'}")
Run Code Online (Sandbox Code Playgroud)

这打印:

      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
Run Code Online (Sandbox Code Playgroud)

基本上发生的事情是dict文字被标记化为键和值,并且它们被推送到堆栈.之后将BUILD_MAP 2两对(键,值)转换为字典.

很可能是堆栈上的数据顺序(这似乎是由dict文字中的键的顺序决定的)和实现细节BUILD_MAP决定了产生的dict键和值.

似乎键值赋值是按照dict文字中定义的顺序完成的.赋值行为与d[key] = value操作相同,所以它基本上是:

  • if key不在dict中(通过相等):add keydo dict
  • 商店valuekey

鉴于{True: 'yes', 1: 'No'}:

  1. 从...开始 {}
  2. (True, 'yes')

    1. 真的不是在dict中 - > (True, hash(True))== (True, 1)是dict中的新键
    2. 关键等于更新值1'yes'
  3. (1, 'no')

    1. 1在dict(1 == True) - >字典中不需要新密钥
    2. 使用值更新等于1(True)的键的值'no'

结果: {True: 'No'}

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


Rem*_*ich 21

True并且1是不同的对象,但它们都具有相同的值:

>>> True is 1
False
>>> True == 1
True
Run Code Online (Sandbox Code Playgroud)

这类似于两个可能具有相同值但仍存储在不同内存位置的字符串:

>>> x = str(12345)
>>> y = str(12345)
>>> x == y 
True
>>> x is y
False
Run Code Online (Sandbox Code Playgroud)

第一个项目被添加到字典中; 然后当添加另一个时,该值已作为键存在.因此密钥更新,密钥值是唯一的.

>>> {x: 1, y: 2}
{"12345": 2}
Run Code Online (Sandbox Code Playgroud)


Tad*_*sen 9

如果密钥已经存在于字典中,则它不会仅覆盖关键值的密钥.

我相信写作x = {True:"a", 1:"b"}是这样的:

x = {}
x[True] = "a"
x[1] = "b"
Run Code Online (Sandbox Code Playgroud)

当它到达x[1] = "b"钥匙时True已经在dict中了,为什么要改变呢?为什么不直接覆盖相关的值.


leo*_*ovp 6

排序似乎是原因.代码示例:

>>> d = {True: 'true', 1: 'one'}
>>> d
{True: 'one'}
>>> d = {1: 'one', True: 'true'}
>>> d
{1: 'true'}
Run Code Online (Sandbox Code Playgroud)

  • @rll:交换订单说明了会发生什么.对于相等的键,最后一个键值获胜,*但仅使用该值*.键不等于不熟悉bools作为int的子类的人也很重要,但*排序*是解释的很大一部分. (6认同)
  • @rll:当然这就是重点.第一个键设置一个值,然后下一个键与第一个键相等*,并为该键设置一个新值. (3认同)