Ответ 1
Словари реализуются как хеш-таблицы, и при добавлении ключей/значений здесь есть два важных понятия: хеширование и равенство.
Чтобы вставить конкретный ключ/значение, 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'
.
-
PyDict_SetItem
вызывается в каждом цикле, чтобы поместить текущие*key
и*value
в словарь. Это та же самая функция, которая вызывается при написанииdictionary[key] = value
. Он вычисляет хэш ключа для разработки, где сначала искать хэш-таблицу, а затем, при необходимости, сравнивать ключ с любым существующим ключом в этой строке (как обсуждалось выше).