Оператор "is" неожиданно ведет себя с целыми числами

Почему в Python происходит следующее непредвиденное поведение?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result
>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?
>>> 257 is 257
True           # Yet the literal numbers compare properly

Я использую Python 2.5.2. Попробовав несколько разных версий Python, оказалось, что Python 2.3.3 демонстрирует указанное выше поведение между 99 и 100.

Основываясь на вышеизложенном, я могу предположить, что Python внутренне реализован таким образом, что "маленькие" целые числа хранятся иначе, чем большие целые числа, и оператор is может определить разницу. Почему дырявая абстракция? Как лучше сравнить два произвольных объекта, чтобы увидеть, одинаковы ли они, если я заранее не знаю, являются ли они числами или нет?

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

user5319825 18.02.2016 15:46

Это специфичная для CPython деталь реализации и неопределенное поведение, используйте с осторожностью.

ospider 05.06.2019 05:38
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
540
2
81 945
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Думаю, ваши гипотезы верны. Эксперимент с id (идентичность объекта):

In [1]: id(255)
Out[1]: 146349024

In [2]: id(255)
Out[2]: 146349024

In [3]: id(257)
Out[3]: 146802752

In [4]: id(257)
Out[4]: 148993740

In [5]: a=255

In [6]: b=255

In [7]: c=257

In [8]: d=257

In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)

Похоже, что числа <= 255 обрабатываются как литералы, а все, что выше, обрабатывается иначе!

Это потому, что объекты, представляющие значения от -5 до +256, создаются во время запуска - и поэтому все использование этих значений используется для предварительно созданного объекта. Почти все ссылки на целые числа вне этого диапазона создают новый внутренний объект каждый раз, когда на них ссылаются. Я думаю, что использование термина «литерал» сбивает с толку - литерал обычно относится к любому значению, набранному в фрагменте кода, поэтому все числа в исходном коде являются литералами.

Tony Suffolk 66 01.07.2018 22:46
Ответ принят как подходящий

Взгляните на это:

>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828

Вот что я нашел в документации Python 2, «Простые целочисленные объекты» (то же самое для Python 3):

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behaviour of Python in this case is undefined. :-)

кто-нибудь знает, как был выбран этот диапазон (-5, 256)? я не был бы слишком удивлен, если бы это было (0, 255) или даже (-255, 255), но диапазон из 262 чисел, начинающийся с -5, кажется удивительно произвольным.

Woodrow Barlow 24.08.2017 21:58

@WoodrowBarlow: Я думаю, что -5 - это просто эвристика для захвата общих отрицательных заполнителей. 0..255 охватывает массивы однобайтовых значений. Это 256, что загадочно, но я предполагаю, что оно предназначено для (диз) сборки целых чисел в / из байтов.

Davis Herring 27.01.2018 04:56

Насколько я понимаю, диапазон был выбран путем изучения часто используемых значений в нескольких проектах (и на нескольких языках).

Tony Suffolk 66 01.07.2018 22:35

Согласно reddit.com/r/Python/comments/18leav/…, раньше диапазон составлял [-5 100]. Он был расширен, чтобы включить полный диапазон значений байтов - плюс 256, потому что это предположительно обычное число.

mwfearnley 08.07.2018 01:47

Почему этот диапазон от -5 до 256?

Ashwani 05.01.2020 21:15

@Ashwani попробуйте прочитать комментарии рядом с вашим комментарием, опубликованным за два года до вашего, и вы найдете ответ на свой вопрос.

jbg 16.05.2020 10:05

Это зависит от того, хотите ли вы увидеть, равны ли две вещи или один и тот же объект.

is проверяет, являются ли они одним и тем же объектом, а не просто равными. Маленькие int, вероятно, указывают на одно и то же место в памяти для экономии места

In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144

Вы должны использовать == для сравнения равенства произвольных объектов. Вы можете указать поведение с помощью атрибутов __eq__ и __ne__.

Поднимите палец вверх за то, что на самом деле объяснили, как сравнивать произвольные объекты, как попросил OP !!

Joooeey 11.04.2020 23:51

Как вы можете проверить в исходный файл вbject.c, Python кэширует небольшие целые числа для повышения эффективности. Каждый раз, когда вы создаете ссылку на маленькое целое число, вы ссылаетесь на кешированное маленькое целое число, а не на новый объект. 257 - немалое целое число, поэтому оно рассчитывается как другой объект.

Для этого лучше использовать ==.

Для объектов с неизменяемыми значениями, таких как целые числа, строки или даты и время, идентификация объекта не особенно полезна. Лучше подумать о равенстве. Идентичность - это, по сути, деталь реализации для объектов значений - поскольку они неизменяемы, нет эффективной разницы между наличием нескольких ссылок на один и тот же объект или несколько объектов.

isявляется - оператор равенства идентичности (функционирующий как id(a) == id(b)); просто два равных числа не обязательно являются одним и тем же объектом. По соображениям производительности некоторые маленькие целые числа являются памятный, поэтому они будут иметь тенденцию быть одинаковыми (это можно сделать, поскольку они неизменяемы).

С другой стороны, оператор PHP=== описывается как проверка равенства и типа: x == y and type(x) == type(y) согласно комментарию Пауло Фрейтаса. Этого будет достаточно для обычных чисел, но будет отличаться от is для классов, которые абсурдно определяют __eq__:

class Unequal:
    def __eq__(self, other):
        return False

Очевидно, что PHP допускает то же самое для «встроенных» классов (которые, как я понимаю, реализованы на уровне C, а не в PHP). Чуть менее абсурдным может быть использование объекта таймера, значение которого меняется каждый раз, когда он используется в качестве числа. Я не знаю, почему вы хотите подражать Visual Basic Now вместо того, чтобы показывать, что это оценка с time.time().

Грег Хьюгилл (OP) сделал один поясняющий комментарий: «Моя цель - сравнить идентичность объектов, а не равенство значений. За исключением чисел, где я хочу рассматривать идентичность объекта так же, как равенство значений».

Это даст еще один ответ, поскольку мы должны категоризировать вещи как числа или нет, чтобы выбрать, сравнивать ли мы с == или is. CPython определяет протокол номера, включая PyNumber_Check, но это недоступно из самого Python.

Мы могли бы попробовать использовать isinstance со всеми известными нам числовыми типами, но это неизбежно будет неполным. Модуль типов содержит список StringTypes, но не NumberTypes. Начиная с Python 2.6, встроенные числовые классы имеют базовый класс numbers.Number, но имеют ту же проблему:

import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)

Кстати, NumPy будет создавать отдельные экземпляры малых чисел.

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

В конце концов, эта проблема связана с тем, что Python изначально не имел дерева типов с такими предикатами, как Схемыnumber? или Haskell'sтип классNum. is проверяет идентичность объекта, а не равенство значений. У PHP также красочная история, где ===, по всей видимости, ведет себя как is только на объектах в PHP5, но не в PHP4. Таковы растущие боли перехода между языками (включая версии одного).

Python's “is” operator behaves unexpectedly with integers?

В заключение - позвольте мне подчеркнуть: Не используйте is для сравнения целых чисел.

Это не поведение, от которого вы должны ожидать.

Вместо этого используйте == и != для сравнения на равенство и неравенство соответственно. Например:

>>> a = 1000
>>> a == 1000       # Test integers like this,
True
>>> a != 5000       # or this!
True
>>> a is 1000       # Don't do this! - Don't use `is` to test integers!!
False

Объяснение

Чтобы знать это, вам необходимо знать следующее.

Во-первых, что делает is? Это оператор сравнения. Из документация:

The operators is and is not test for object identity: x is y is true if and only if x and y are the same object. x is not y yields the inverse truth value.

Итак, следующие эквивалентны.

>>> a is b
>>> id(a) == id(b)

Из документация:

id Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.

Обратите внимание, что тот факт, что идентификатор объекта в CPython (эталонная реализация Python) является местом в памяти, является деталью реализации. Другие реализации Python (например, Jython или IronPython) могут легко иметь другую реализацию для id.

Итак, каков вариант использования is? PEP8 описывает:

Comparisons to singletons like None should always be done with is or is not, never the equality operators.

Вопрос

Вы задаете и формулируете следующий вопрос (с кодом):

Why does the following behave unexpectedly in Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result

Ожидаемый результат - нет. Почему это ожидается? Это означает только то, что целые числа со значением 256, на которые ссылаются как a, так и b, являются одним и тем же экземпляром целого числа. Целые числа неизменяемы в Python, поэтому они не могут измениться. Это не должно повлиять на какой-либо код. Этого не следует ожидать. Это просто деталь реализации.

Но, возможно, нам стоит радоваться, что в памяти не появляется новый отдельный экземпляр каждый раз, когда мы указываем значение, равное 256.

>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?

Похоже, теперь у нас есть два отдельных экземпляра целых чисел со значением 257 в памяти. Поскольку целые числа неизменяемы, это расходует память. Будем надеяться, что мы не тратим много зря. Вероятно, нет. Но такое поведение не гарантируется.

>>> 257 is 257
True           # Yet the literal numbers compare properly

Что ж, похоже, что ваша конкретная реализация Python пытается быть умной и не создает в памяти целые числа с избыточным значением, если это не нужно. Вы, кажется, указываете, что используете референтную реализацию Python, то есть CPython. Подходит для CPython.

Было бы даже лучше, если бы CPython мог делать это глобально, если бы он мог делать это дешево (поскольку это потребовало бы затрат при поиске), возможно, другая реализация могла бы это сделать.

Но что касается воздействия на код, вам не нужно заботиться о том, является ли целое число конкретным экземпляром целого числа. Вам следует заботиться только о том, каково значение этого экземпляра, и вы должны использовать для этого обычные операторы сравнения, то есть ==.

Что делает is

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

>>> a is b

такой же как

>>> id(a) == id(b)

Тогда зачем нам использовать is?

Это может быть очень быстрая проверка по сравнению, скажем, с проверкой равенства двух очень длинных строк по значению. Но поскольку это относится к уникальности объекта, у нас есть ограниченные варианты использования для него. Фактически, мы в основном хотим использовать его для проверки наличия None, который является синглтоном (единственный экземпляр, существующий в одном месте в памяти). Мы могли бы создать другие синглтоны, если есть возможность объединить их, что мы можем проверить с помощью is, но они относительно редки. Вот пример (будет работать в Python 2 и 3), например.

SENTINEL_SINGLETON = object() # this will only be created one time.

def foo(keyword_argument=None):
    if keyword_argument is None:
        print('no argument given to foo')
    bar()
    bar(keyword_argument)
    bar('baz')

def bar(keyword_argument=SENTINEL_SINGLETON):
    # SENTINEL_SINGLETON tells us if we were not passed anything
    # as None is a legitimate potential argument we could get.
    if keyword_argument is SENTINEL_SINGLETON:
        print('no argument given to bar')
    else:
        print('argument to bar: {0}'.format(keyword_argument))

foo()

Какие отпечатки:

no argument given to foo
no argument given to bar
argument to bar: None
argument to bar: baz

Итак, мы видим, что с is и дозорным мы можем различать, когда bar вызывается без аргументов, и когда он вызывается с None. Это основные варианты использования is - использует ли нет его для проверки равенства целых чисел, строк, кортежей или других подобных вещей.

«Это основные варианты использования is - не используйте его для проверки равенства целых чисел, строк, кортежей или других подобных вещей». Однако я пытаюсь интегрировать простой конечный автомат в свой класс, и, поскольку состояния являются непрозрачными значениями, единственное наблюдаемое свойство которых - быть одинаковыми или разными, для них вполне естественно быть сопоставимыми с is. Я планирую использовать интернированные строки как состояния. Я бы предпочел простые целые числа, но, к сожалению, Python не может вставлять целые числа (0 is 0 - деталь реализации).

Alexey 03.05.2019 15:28

@Alexey звучит так, будто вам нужны перечисления? stackoverflow.com/questions/37601644/…

Aaron Hall 03.05.2019 15:47

Может, спасибо, не знал о них. Это могло бы быть подходящим дополнением к вашему ответу ИМО.

Alexey 03.05.2019 16:05

Возможно, использование в вашем ответе нескольких глупых объектов, таких как часовой, было бы более легким решением ...

Alexey 03.05.2019 16:10

Перечисления @Alexey находятся в стандартной библиотеке Python 3, и это, вероятно, побудит ваш код быть немного более значимым, чем голые часовые.

Aaron Hall 03.05.2019 16:52

Хорошо, я убежден попробовать Enum. Думаю, я воспользуюсь object() для создания значений «перечисляемых» атрибутов.

Alexey 03.05.2019 17:03

А как насчет is с логическими значениями? Нравится: if (a is None) is (b is None): raise ValueError.

Alexey 09.05.2019 07:29

То же самое и со строками:

>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Теперь вроде все нормально.

>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Это тоже ожидается.

>>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, True, 4555308080, 4555308080)

>>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, False, 4555308176, 4555308272)

Это неожиданно.

Произошло такое - согласился, что еще страннее. Так что я поигрался с этим, и это еще более странно - связано с космосом. Например, строка 'xx' соответствует ожидаемой, как и 'xxx', но не 'x x'.

Brian 16.12.2015 23:48

Это потому, что он выглядит как символ, если в нем нет места. Имена автоматически интернируются, поэтому, если где-нибудь в вашем сеансе Python есть что-нибудь с именем xx, эта строка уже интернирована; и может быть эвристика, которая сделает это, если оно просто похоже на имя. Как и в случае с числами, это можно сделать, потому что они неизменяемы. docs.python.org/2/library/functions.html#internguilload.com/python-string-interning

Yann Vernier 22.09.2016 10:48

Я опаздываю, но вам нужен источник с вашим ответом? Я постараюсь сформулировать это во вводной манере, чтобы больше людей могли следить за мной.


В CPython хорошо то, что вы действительно можете видеть источник этого. Я собираюсь использовать ссылки для выпуска 3.5, но найти соответствующие 2.x - тривиально.

В CPython функция C-API, которая обрабатывает создание нового объекта int, называется PyLong_FromLong(long v). Описание этой функции:

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behaviour of Python in this case is undefined. :-)

(Мой курсив)

Не знаю, как вы, но я это вижу и думаю: Давайте найдем этот массив!

Если вы не возились с кодом C, реализующим CPython вам следует; все довольно организовано и читаемо. В нашем случае нам нужно посмотреть Подкаталог Objects из основное дерево каталогов исходного кода.

PyLong_FromLong имеет дело с объектами long, поэтому нетрудно сделать вывод, что нам нужно заглянуть внутрь longobject.c. Заглянув внутрь, вы можете подумать, что все в порядке; они есть, но не бойтесь, функция, которую мы ищем, пугает в строка 230, ожидая, когда мы ее проверим. Это небольшая функция, поэтому основное тело (за исключением объявлений) легко вставляется сюда:

PyObject *
PyLong_FromLong(long ival)
{
    // omitting declarations

    CHECK_SMALL_INT(ival);

    if (ival < 0) {
        /* negate: cant write this as abs_ival = -ival since that
           invokes undefined behaviour when ival is LONG_MIN */
        abs_ival = 0U-(unsigned long)ival;
        sign = -1;
    }
    else {
        abs_ival = (unsigned long)ival;
    }

    /* Fast path for single-digit ints */
    if (!(abs_ival >> PyLong_SHIFT)) {
        v = _PyLong_New(1);
        if (v) {
            Py_SIZE(v) = sign;
            v->ob_digit[0] = Py_SAFE_DOWNCAST(
                abs_ival, unsigned long, digit);
        }
        return (PyObject*)v; 
}

Итак, мы не C мастер-код-haxxorz, но мы также не тупые, мы видим, что CHECK_SMALL_INT(ival); соблазнительно смотрит на всех нас; мы можем понять, что это как-то связано с этим. Давайте проверим:

#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } while(0)

Таким образом, это макрос, который вызывает функцию get_small_int, если значение ival удовлетворяет условию:

if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)

Так что же такое NSMALLNEGINTS и NSMALLPOSINTS? Макросы! Они здесь:

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

Итак, наше условие - if (-5 <= ival && ival < 257) вызов get_small_int.

Теперь давайте посмотрим на get_small_int во всей красе (ну, мы просто посмотрим на его тело, потому что там находятся интересные вещи):

PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);

Хорошо, объявите PyObject, подтвердите выполнение предыдущего условия и выполните присвоение:

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

small_ints очень похож на тот массив, который мы искали, и это так! Мы могли бы просто прочитать проклятую документацию и все время знать!:

/* Small integers are preallocated in this array so that they
   can be shared.
   The integers that are preallocated are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

Так что да, это наш парень. Если вы хотите создать новый int в диапазоне [NSMALLNEGINTS, NSMALLPOSINTS), вы просто получите ссылку на уже существующий объект, который был предварительно выделен.

Поскольку ссылка относится к одному и тому же объекту, выдача id() напрямую или проверка идентичности с помощью is на нем вернет точно то же самое.

Но когда они распределяются ??

Во время инициализации в _PyLong_Init Python с радостью войдет в цикл for, чтобы сделать это за вас:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {

Ознакомьтесь с исходным кодом, чтобы прочитать тело цикла!

Я надеюсь, что мое объяснение теперь ясно дало вам представление о C (очевидно, каламбур).


Но, 257 is 257? Как дела?

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

>>> 257 is 257

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

>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)

Когда CPython выполнит операцию, он просто загрузит тот же самый объект:

>>> import dis
>>> dis.dis(codeObj)
  1           0 LOAD_CONST               0 (257)   # dis
              3 LOAD_CONST               0 (257)   # dis again
              6 COMPARE_OP               8 (is)

Итак, is вернет True.

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


Во-первых, есть некоторые другие заранее созданные значения, такие как пустые tuple, str и bytes, и несколько коротких строк (в CPython 3.6 это 256 односимвольных строк Latin-1). Например:

>>> a = ()
>>> b = ()
>>> a is b
True

Но также могут быть идентичными даже не созданные заранее значения. Рассмотрим эти примеры:

>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True

И это не ограничивается значениями int:

>>> g, h = 42.23e100, 42.23e100
>>> g is h
True

Очевидно, CPython не имеет заранее созданного значения float для 42.23e100. Итак, что здесь происходит?

Компилятор CPython объединит постоянные значения некоторых известных неизменяемых типов, таких как int, float, str, bytes, в одном модуле компиляции. Для модуля весь модуль является единицей компиляции, но в интерактивном интерпретаторе каждый оператор является отдельной единицей компиляции. Поскольку c и d определены в отдельных операторах, их значения не объединяются. Поскольку e и f определены в одном операторе, их значения объединяются.


Вы можете увидеть, что происходит, разобрав байт-код. Попробуйте определить функцию, которая выполняет e, f = 128, 128, а затем вызвать для нее dis.dis, и вы увидите, что есть одно постоянное значение (128, 128).

>>> def f(): i, j = 258, 258
>>> dis.dis(f)
  1           0 LOAD_CONST               2 ((128, 128))
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (i)
              6 STORE_FAST               1 (j)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> f.__code__.co_consts
(None, 128, (128, 128))
>>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1])
4305296480, 4305296480, 4305296480

Вы можете заметить, что компилятор сохранил 128 как константу, хотя на самом деле он не используется байт-кодом, что дает вам представление о том, насколько мало оптимизирует компилятор CPython. Это означает, что (непустые) кортежи фактически не объединяются:

>>> k, l = (1, 2), (1, 2)
>>> k is l
False

Поместите это в функцию, dis it, и посмотрите на co_consts - есть 1 и 2, два кортежа (1, 2), которые используют одни и те же 1 и 2, но не идентичны, и кортеж ((1, 2), (1, 2)), который имеет два разных равных кортежа.


CPython выполняет еще одну оптимизацию: интернирование строк. В отличие от сворачивания констант компилятора, это не ограничивается литералами исходного кода:

>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True

С другой стороны, он ограничен типом str и строками внутреннее хранилище типа "ascii compact", "compact" или "legacy ready", и во многих случаях будет интернирован только «ascii compact».


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

Может быть, стоит изучить правила для одного конкретного Python ради удовольствия. Но полагаться на них в своем коде не стоит. Единственное безопасное правило:

  • Не пишите код, предполагающий, что два равных, но отдельно созданных неизменяемых значения идентичны (не используйте x is y, используйте x == y)
  • Не пишите код, предполагающий, что два равных, но отдельно созданных неизменяемых значения различны (не используйте x is not y, используйте x != y)

Или, другими словами, используйте is только для проверки задокументированных синглтонов (например, None) или созданных только в одном месте кода (например, идиома _sentinel = object()).

Менее загадочный совет прост: не используйте x is y для сравнения, используйте x == y. Точно так же не используйте x is not y, используйте x != y.

smci 09.03.2020 09:40

Итак, глядя на этот вопрос, почему a=257; b=257 на одной строке a is b True

Joe 07.06.2020 00:05

Что нового в Python 3.8: изменения в поведении Python:

The compiler now produces a SyntaxWarning when identity checks (is and is not) are used with certain types of literals (e.g. strings, ints). These can often work by accident in CPython, but are not guaranteed by the language spec. The warning advises users to use equality tests (== and !=) instead.

Другие вопросы по теме