Пользовательский объект на месте, распаковывающий другое поведение с __getitem__ python 3.5 против python 3.6 - PullRequest
0 голосов
/ 17 мая 2018

следующий вопрос по этот вопрос : я запустил приведенный ниже код на 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: объект типа получил несколько значений для аргумента ключевого слова 'b'

на 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 Ответ

0 голосов
/ 17 мая 2018

На самом деле это сложный конфликт между несколькими внутренними операциями при распаковке пользовательского объекта сопоставления и создании аргументов вызывающей стороны. Поэтому, если вы хотите полностью понять основные причины, я бы посоветовал вам изучить исходный код. Тем не менее, вот некоторые советы и отправные точки, которые вы можете изучить для более подробной информации.

Внутренне, когда вы распаковываете на уровне вызывающего, байт-код 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.

...