На месте пользовательский объект распаковки другое поведение с getitem python 3.5 против python 3.6
последующий вопрос о этот вопрос: я запустил код ниже на python 3.5 и python 3.6 - с очень разными результатами:
class Container:
KEYS = ('a', 'b', 'c')
def __init__(self, a=None, b=None, c=None):
self.a = a
self.b = b
self.c = c
def keys(self):
return Container.KEYS
def __getitem__(self, key):
if key not in Container.KEYS:
raise KeyError(key)
return getattr(self, key)
def __str__(self):
# python 3.6
# return f'{self.__class__.__name__}(a={self.a}, b={self.b}, c={self.c})'
# python 3.5
return ('{self.__class__.__name__}(a={self.a}, b={self.b}, '
'c={self.c})').format(self=self)
data0 = Container(a=1, b=2, c=3)
print(data0)
data3 = Container(**data0, b=7)
print(data3)
как указано в предыдущем вопросе, это вызывает
ошибку TypeError: объект типа есть несколько значений для аргумента 'б'
на python 3.6. но на Python 3.5, я получаю исключение:
KeyError: 0
более того, если я это сделаю не поднимать KeyError
но просто распечатать key
и return
на __getitem__
:
def __getitem__(self, key):
if key not in Container.KEYS:
# raise KeyError(key)
print(key)
return
return getattr(self, key)
это выводит int
последовательность 0, 1, 2, 3, 4, ...
. (python 3.5)
Итак, мои вопросы:
что изменилось между версиями, что делает это вести себя так по-разному?
откуда берутся эти целые числа?
обновление : как упомянуто в комментарий λuser реализация __iter__
изменит поведение на python 3.5, чтобы соответствовать тому, что делает python 3.6:
def __iter__(self):
return iter(Container.KEYS)
1 ответов
на самом деле это сложный конфликт между несколькими внутренними операциями при распаковке пользовательского объекта сопоставления и создании аргументов вызывающего. Поэтому, если вы хотите полностью понять основные причины, я бы предложил вам изучить исходный код. Однако вот некоторые подсказки и отправные точки, которые вы можете изучить для получения более подробной информации.
внутренне, когда вы распаковываете на уровне вызывающего абонента, байтовый код 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 как до __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
который отвечает за создание кортежа и потребление элементов count из стека.
BUILD_TUPLE (count)
создает кортеж, потребляющий элементы count из стека, и толкает > результирующий Кортеж на стек.
и это, ИМО, почему вы не получаете ключевую ошибку, а вместо этого получаете TypeError
. Потому что во время создания кортежа аргументов он сталкивается с повторяющимся именем и, следовательно, правильно возвращает TypeError
.