Кэшированные целые числа, оператор `is` и `id()` в Python 3.7

Раньше я показывал что-то вроде print(5 is 7 - 2, 300 is 302 - 2) в своих выступлениях на Python, когда говорил о некоторых мелочах Python. Сегодня я понял, что этот пример дает неожиданный (для меня) результат при запуске в Python 3.7.

Мы знаем, что числа от -5 до 255 кэшируются внутри Python 3. docs — PyLong_FromLong, который также можно найти в более ранних документах по API.

Оператор is (как описано в документах Python 3 docs — is operator ) проверяет идентичность объекта, т. е. использует функцию id(), чтобы определить это, и возвращает True, когда значения совпадают.

Функция id() гарантированно возвращает уникальное и постоянное значение для объекта в течение его жизни (также описано в документах Документация по Python 3 — id()).

Все эти правила дают вам следующие результаты (как известно многим программистам Python):

Питон 2.7:

>>> print(5 is 7 - 2, 300 is 302 - 2)
True False

Питон 3.6:

>>> print(5 is 7 - 2, 300 is 302 - 2)
True False

Однако Python 3.7 ведет себя иначе:

>>> print(5 is 7 - 2, 300 is 302 - 2)
True True

Я пытался понять, почему, но пока не нашел подсказок в исходниках Python...

id(302 - 2) всегда дает другое значение, поэтому мне интересно, почему 302 - 2 is 300 дает True. Как оператор is узнает, что значения совпадают? Это как-то перегружено для целочисленных сравнений в Python 3.7?

>>> id(300)
140059023515344

>>> id(302 - 2)
140059037091600

>>> id(300) is id(302 - 2)
False

>>> 300 is 302 - 2
True

>>> id(300) == id(302 -2)
True

>>> id(302 - 2)
140059037090320

>>> id(302 - 2)
140059023514640

person tamasgal    schedule 02.04.2019    source источник
comment
Согласно примечанию 4 Благодаря автоматической сборке мусора списков и динамической природы дескрипторов, вы можете заметить, казалось бы, необычное поведение при определенных применениях оператора is, например при сравнении методов экземпляра или констант. Проверьте их документацию для получения дополнительной информации. Но я проверил документы int и целочисленную часть стандартных типов и не нашел ничего, что действительно отвечало бы этому. Приятно найти ответ   -  person Jab    schedule 03.04.2019
comment
Спасибо за это, хорошо иметь эту ссылку здесь :) user2357112 уже ответил на вопрос, смотрите ниже!   -  person tamasgal    schedule 03.04.2019
comment
Это то, что касается деталей реализации. Они подлежат изменению. В данном случае изменился компилятор, который переключился с оптимизатора глазка на тот, который оптимизирует сгенерированный AST. См. import dis; dis.dis.dis(compile('print(5 is 7 - 2, 300 is 302 - 2)', '', 'single')), чтобы узнать, как это повлияет на ваш образец. Обратите особое внимание на номер индекса после каждого кода операции LOAD_CONST.   -  person Martijn Pieters    schedule 03.04.2019


Ответы (1)


is не изменилось. Никакая часть языковой семантики не изменилась; никогда не указывалось, являются ли объекты, которые вы сравниваете, одним и тем же объектом. Две стороны вашего is сравнения оказались теперь одним и тем же объектом. Это результат изменения постоянной оптимизации фолдинга.

Первоначальное создание co_consts объекта кода повторно использует один объект для эквивалентных атомарных констант. (Я говорю «эквивалентно» вместо «равно», потому что 1 и 1.0 не эквивалентны.) Это эффект, отличный от кэширования целых чисел от -5 до 256, и он применяется только в пределах одного объекта кода. Ранее этап оптимизации во время компиляции это преобразование 302 - 2 в 300 произошло в оптимизаторе глазка байт-кода, который срабатывает после первоначальной генерации co_consts и не выполняет такое же постоянное повторное использование.

В CPython 3.7 этот этап оптимизации был перемещен из оптимизатора байт-кода в новый AST. оптимизатор. Оптимизатор AST вступает в силу до начальной генерации co_consts объекта кода, поэтому теперь к результатам применяется постоянное повторное использование.


Вы можете увидеть последствия постоянного повторного использования старых версий Python, выполнив что-то вроде

>>> 300 is 300
True

который выдает True даже на CPython 2.7 или 3.6, несмотря на то, что 300 находится за пределами диапазона кэша малых целых чисел. Вы можете предотвратить постоянное повторное использование, гарантируя, что константы, которые вы сравниваете, заканчиваются в отдельных объектах кода:

>>> (lambda: 300)() is 300
False

Это дает False в любой версии CPython, даже с новыми изменениями оптимизатора. Однако он выдает True для PyPy, потому что PyPy имеет собственное поведение оптимизации, а PyPy ведет себя так, как будто все равные целые числа представлены одним и тем же целочисленным объектом.

person user2357112 supports Monica    schedule 02.04.2019
comment
А логично.. Можно было бы подумать о постоянном сворачивании. Спасибо! - person tamasgal; 03.04.2019
comment
Подсказка: запусти import dis; dis.dis(compile('print(5 is 7 - 2, 300 is 302 - 2)', '', 'single')) и он сразу бы выскочил. :-) - person Martijn Pieters; 03.04.2019
comment
Это правда Мартейн ;) - person tamasgal; 03.04.2019
comment
Во всех версиях Python вы увидите, что LOAD_CONST загружает свернутые константы, просто так получилось, что новый оптимизатор знает, что нужно создавать только одну константу, а не две, для эквивалентных значений. Вы можете увидеть это из постоянного индекса, из которого загружается LOAD_CONST. - person Martijn Pieters; 03.04.2019