На самом деле это сложный конфликт между несколькими внутренними операциями при распаковке пользовательского объекта сопоставления и создании аргументов вызывающей стороны. Поэтому, если вы хотите полностью понять основные причины, я бы посоветовал вам изучить исходный код. Тем не менее, вот некоторые советы и отправные точки, которые вы можете изучить для более подробной информации.
Внутренне, когда вы распаковываете на уровне вызывающего, байт-код BUILD_MAP_UNPACK_WITH_CALL(count)
выскакивает считает отображений из стека, объединяет их в один словарь и помещает результат. С другой стороны, эффект стека этого кода операции с аргументом oparg
определяется следующим образом: :
case BUILD_MAP_UNPACK_WITH_CALL:
return 1 - oparg;
С учетом сказанного давайте посмотрим на байтовые коды примера (в Python-3.5), чтобы увидеть это в действии:
>>> def bar(data0):foo(**data0, b=4)
...
>>>
>>> dis.dis(bar)
1 0 LOAD_GLOBAL 0 (foo)
3 LOAD_FAST 0 (data0)
6 LOAD_CONST 1 ('b')
9 LOAD_CONST 2 (4)
12 BUILD_MAP 1
15 BUILD_MAP_UNPACK_WITH_CALL 258
18 CALL_FUNCTION_KW 0 (0 positional, 0 keyword pair)
21 POP_TOP
22 LOAD_CONST 0 (None)
25 RETURN_VALUE
>>>
Как видите, по смещению 15 у нас есть BUILD_MAP_UNPACK_WITH_CALL
байт-код, который отвечает за распаковку.
Что теперь происходит, когда он возвращает 0 в качестве аргумента key
методу __getitem__
?
Всякий раз, когда интерпретатор встречает исключение во время распаковки, которое в данном случае является KeyError
, он прекращает продолжение потока push / pop и вместо возврата реального значения вашей переменной возвращает эффект стека, поэтому ключ сначала равен 0, и если вы не обрабатываете исключение каждый раз, когда получаете увеличенный результат (из-за размера стека).
Теперь, если вы сделаете такую же разборку в Python-3.6, вы получите следующий результат:
>>> dis.dis(bar)
1 0 LOAD_GLOBAL 0 (foo)
2 BUILD_TUPLE 0
4 LOAD_FAST 0 (data0)
6 LOAD_CONST 1 ('b')
8 LOAD_CONST 2 (4)
10 BUILD_MAP 1
12 BUILD_MAP_UNPACK_WITH_CALL 2
14 CALL_FUNCTION_EX 1
16 POP_TOP
18 LOAD_CONST 0 (None)
20 RETURN_VALUE
Перед созданием локальных переменных (LOAD_FAST
) и после LOAD_GLOBAL
существует BUILD_TUPLE
, который отвечает за создание кортежа и потребление элементов из стека.
* +1039 * BUILD_TUPLE (количество)
Создает кортеж, потребляющий количество элементов из стека, и помещает полученный> кортеж в стек.
И вот, IMO, почему вы не получаете ключевую ошибку, а вместо этого вы получаете TypeError
. Потому что во время создания кортежа аргументов он встречает повторяющееся имя и, следовательно, правильно возвращает TypeError
.