Необычное поведение оператора 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
>>>