Необычное поведение оператора IS в python

Из некоторых ответов на Stackoverflow я узнал, что с -5 до 256 одинаковых мест памяти ссылается, таким образом, мы получаем правду:

>>> a = 256
>>> a is 256
True

Теперь идет поворот (см. Эту строку перед маркировкой дубликата):

>>> a = 257
>>> a is 257 
False 

Это полностью понято, но теперь, если я это сделаю:

>>> a = 257; a is 257
True
>>> a = 12345; a is 12345
True

Зачем?

Ответы

Ответ 1

То, что вы видите, - это оптимизация в компиляторе в CPython (который компилирует ваш исходный код в байт-код, который выполняется интерпретатором). Всякий раз, когда одно и то же неизменяемое постоянное значение используется в нескольких разных местах внутри фрагмента кода, который скомпилируется за один шаг, компилятор попытается использовать ссылку на тот же объект для каждого места.

Поэтому, если вы выполняете несколько назначений в одной строке в интерактивном сеансе, вы получите две ссылки на один и тот же объект, но не будете, если используете две отдельные строки:

>>> x = 257; y = 257  # multiple statements on the same line are compiled in one step
>>> print(x is y)     # prints True
>>> x = 257
>>> y = 257
>>> print(x is y)     # prints False this time, since the assignments were compiled separately

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

def foo():
    x = 257
    y = 257
    return x is y  # this will always return True

Хотя интересно исследовать такие оптимизации, как этот, вы никогда не должны полагаться на это поведение в своем обычном коде. Различные интерпретаторы Python и даже разные версии CPython могут делать эти оптимизации по-разному или совсем не. Если ваш код зависит от конкретной оптимизации, он может быть полностью нарушен для кого-то другого, кто пытается запустить его в своей собственной системе.

В качестве примера два назначения в одной строке, которые я показываю в моем первом блоке кода выше, не приводят к двум ссылкам на один и тот же объект, когда я делаю это в интерактивной оболочке внутри Spyder (моя предпочтительная IDE). Я понятия не имею, почему эта конкретная ситуация работает не так, как в обычной интерактивной оболочке, но по-разному это моя ошибка, поскольку мой код зависит от поведения, специфичного для реализации.

Ответ 2

После обсуждения и тестирования в различных версиях можно сделать окончательные выводы.

Python будет интерпретировать и компилировать инструкции в блоках. В зависимости от используемого синтаксиса, версия Python, операционная система, распределение, разные результаты могут быть достигнуты в зависимости от того, какие команды Python берет в одном блоке.

Общие правила:

(из официальной документации)

Текущая реализация сохраняет массив целых объектов для всех целых чисел между -5 и 256

Следовательно:

a = 256
id(a)
Out[2]: 1997190544
id(256)
Out[3]: 1997190544 # int actually stored once within Python

a = 257
id(a)
Out[5]: 2365489141456
id(257)
Out[6]: 2365489140880 #literal, temporary. as you see the ids differ
id(257)
Out[7]: 2365489142192 # literal, temporary. as you see it gets a new id everytime
                      # since it is not pre-stored

Часть ниже возвращает False в Python 3.6.3 | Anaconda custom (64-bit) | (по умолчанию, 17 октября 2017, 23:26:12) [MSC v.1900 64 бит (AMD64)]

a = 257; a is 257
Out[8]: False

Но

a=257; print(a is 257) ; a=258; print(a is 257)
>>>True
>>>False

Как видно, все, что Python берет в "одном блоке", не является детерминированным и может колебаться в зависимости от того, как оно написано, одной строки или нет, а также используемой версии, операционной системы и дистрибутива.

Ответ 3

Из документов python2:

Операторы являются и не проверяют идентификатор объекта: x является y истинным тогда и только тогда, когда x и y являются одним и тем же объектом. x не означает, что y дает обратное значение истины. [6]

Из документов python3:

Операторы являются и не проверяют идентификатор объекта: x является y истинным тогда и только тогда, когда x и y являются одним и тем же объектом. Идентификатор объекта определяется с помощью функции id(). x не означает, что y дает обратное значение истины. [4]

Таким образом, в основном ключом к пониманию тех тестов, которые вы запускали на консоли repl, является использование функции id(), вот пример, который покажет вам, что происходит за шторами:

>>> a=256
>>> id(a);id(256);a is 256
2012996640
2012996640
True
>>> a=257
>>> id(a);id(257);a is 257
36163472
36162032
False
>>> a=257;id(a);id(257);a is 257
36162496
36162496
True
>>> a=12345;id(a);id(12345);a is 12345
36162240
36162240
True

Тем не менее, как правило, хороший способ понять, что происходит за шторами с этими типами фрагментов, - либо с помощью dis.dis, либо dis.disco, давайте посмотрим, например, как выглядит этот фрагмент:

import dis
import textwrap

dis.disco(compile(textwrap.dedent("""\
    a=256
    a is 256
    a=257
    a is 257
    a=257;a is 257
    a=12345;a is 12345\
"""), '', 'exec'))

выход будет:

  1           0 LOAD_CONST               0 (256)
              2 STORE_NAME               0 (a)

  2           4 LOAD_NAME                0 (a)
              6 LOAD_CONST               0 (256)
              8 COMPARE_OP               8 (is)
             10 POP_TOP

  3          12 LOAD_CONST               1 (257)
             14 STORE_NAME               0 (a)

  4          16 LOAD_NAME                0 (a)
             18 LOAD_CONST               1 (257)
             20 COMPARE_OP               8 (is)
             22 POP_TOP

  5          24 LOAD_CONST               1 (257)
             26 STORE_NAME               0 (a)
             28 LOAD_NAME                0 (a)
             30 LOAD_CONST               1 (257)
             32 COMPARE_OP               8 (is)
             34 POP_TOP

  6          36 LOAD_CONST               2 (12345)
             38 STORE_NAME               0 (a)
             40 LOAD_NAME                0 (a)
             42 LOAD_CONST               2 (12345)
             44 COMPARE_OP               8 (is)
             46 POP_TOP
             48 LOAD_CONST               3 (None)
             50 RETURN_VALUE

Как мы видим, в этом случае выход asm не говорит нам очень много, мы можем видеть, что строки 3-4 в основном являются "теми же" инструкциями, что и строка 5. Таким образом, моя рекомендация снова будет использовать id() так что вы будете знать, что is будет сравнивать. Если вы хотите точно знать, какой тип оптимизаций выполняет cpython, я боюсь, вам нужно будет выкопать его в исходном коде

Ответ 4

Вообще говоря, цифры вне диапазона -5 до 256 не обязательно будут иметь оптимизацию, применяемую к числам в этом диапазоне. Тем не менее, Python может свободно применять другие оптимизации. В вашей причине вы видите, что одно и то же буквальное значение, используемое несколько раз в одной строке, сохраняется в одном месте памяти независимо от того, сколько раз оно использовалось в этой строке. Вот несколько других примеров такого поведения:

>>> s = 'a'; s is 'a'
True
>>> s = 'asdfghjklzxcvbnmsdhasjkdhskdja'; s is 'asdfghjklzxcvbnmsdhasjkdhskdja'
True
>>> x = 3.14159; x is 3.14159
True
>>> t = 'a' + 'b'; t is 'a' + 'b'
True
>>>