Выбор, сделанный Python 3.5, чтобы выбрать ключи при сравнении их в словаре



при построении словаря следующим образом:



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


когда я запускаю его в интерактивном интерпретаторе Python, дикт представлен следующим образом:



dict = {True: 'No'}


Я понимаю, что значения True и 1 равны из-за приведения типа, потому что при сравнении числовых типов суженный тип расширяется до другого типа (boolean является дочерним целым числом). Так как я понял из документации, когда мы вводим True == 1 Python конвертирует True to 1 и сравнивает их.



чего я не понимаю, почему True выбирается в качестве ключа вместо 1.



Я что-то пропустил?

473   6  

6 ответов:

словари реализованы в виде хэш-таблиц и есть два важных понятия при добавлении ключей / значений здесь:хеширования и равенство.

чтобы вставить определенный ключ / значение, 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, чтобы увидеть, что создание словаря выглядит ниже уровня surface-Python, вот более подробно.

  • Python-код {True: 'yes', 1: 'No'} разбирается в токены и отдаются компилятору. Учитывая синтаксис, Python знает, что словарь должен быть создан с использованием значений внутри фигурных скобок. Байтовый код для загрузки четырех значений в стек виртуальной машины (LOAD_CONST), а затем построить словарь (BUILD_MAP) стоит в очереди.

  • четыре постоянных значения помещаются в верхнюю часть стека в том порядке, в котором они видны:

    'No'
    1
    'yes'
    True
    
  • код 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();
    }
    

три ключевых шага здесь заключаются в следующем:

  1. пустая хэш-таблица создается с помощью _PyDict_NewPresized. Небольшие словари (всего несколько элементов, как 2 в этом случае) нужна таблица с восемью строками.

  2. The 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'.

  1. PyDict_SetItem вызывается в каждом цикле поставить тег *key и *value в словарь. Это та же функция, которая вызывается при записи dictionary[key] = value. Он вычисляет хэш ключа, чтобы определить, где искать сначала в хэш-таблице, а затем, если необходимо, сравнить ключ с любым существующим ключом в этой строке (как описано выше).

основное помещение - 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'}:

  1. Начнем с {}
  2. добавить (True, 'yes')

    1. правда не в дикт -> (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, оно может отличаться в других реализациях интерпретатора.

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}

если ключ уже присутствует в словаре, он не переопределяет ключ только связанное значение.

Я считаю, что писать!--1--> это по строкам:

x = {}
x[True] = "a"
x[1] = "b"

и до достижения x[1] = "b" ключ True уже в диктанте, так зачем его менять? почему бы просто не переопределить значения.

заказ, кажется, причина. Пример кода:

>>> d = {True: 'true', 1: 'one'}
>>> d
{True: 'one'}
>>> d = {1: 'one', True: 'true'}
>>> d
{1: 'true'}

Это неоднозначное утверждение.

логика: d = { True: 'no', 1: 'yes'}

когда python вычисляет выражение, он делает это последовательно, поэтому он эквивалентен этому.

d = dict() d[True] = 'no' d[1] = 'yes'

константа True является ключом, но она оценивается в 1, поэтому вы просто устанавливаете значение для ключа дважды.

Comments

    Ничего не найдено.