Оператор "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 Оператор может сказать разницу. Почему дырявая абстракция? Что может быть лучше для сравнения двух произвольных объектов, чтобы увидеть, являются ли они одинаковыми, когда я не знаю заранее, являются ли они числами или нет?

11 ответов

Решение

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

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

РЕДАКТИРОВАТЬ: Вот то, что я нашел в документации по Python 2, "Простые целочисленные объекты" (то же самое для Python 3):

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

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

В заключение - позвольте мне подчеркнуть: не использовать 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 делать? Это оператор сравнения. Из документации:

Операторы is а также is not тест на идентичность объекта: x is y Истинно, если и только если x и y - один и тот же объект. x is not y дает обратное значение истины.

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

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

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

id Вернуть "личность" объекта. Это целое число (или длинное целое число), которое гарантированно будет уникальным и постоянным для этого объекта в течение срока его службы. Два объекта с неперекрывающимися временами жизни могут иметь одинаковые id() значение.

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

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

Сравнение с синглтонами вроде None всегда должно быть сделано с is или же is not Никогда операторы равенства.

Вопрос

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

Почему следующее поведение неожиданно ведет себя в 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 - не используйте его для проверки на равенство целых чисел, строк, кортежей или других подобных вещей.

Я опоздал, но вы хотите какой-нибудь источник с вашим ответом? *

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

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

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

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

Если вы не возились с C Код, реализующий CPython, вы должны, все довольно организовано и читабельно. Для нашего случая нам нужно посмотреть в Objects/ подкаталог основного дерева каталогов исходного кода.

PyLong_FromLong имеет дело с long объекты, поэтому не должно быть трудно сделать вывод, что нам нужно заглянуть внутрь longobject.c, Заглянув внутрь, вы можете подумать, что все хаотично; они есть, но не бойтесь, функция, которую мы ищем, охлаждает line 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 master-code-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++) {
    // Look me up!
}

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


Но 257 это 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,


* - Я постараюсь изложить это более вводным образом, чтобы большинство из них могло следовать за ним.

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

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

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

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

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

Лучше использовать == для этой цели.

Я думаю, что ваши гипотезы верны. Эксперимент с 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 трактуются как литералы, а все вышеперечисленное трактуется иначе!

Есть еще одна проблема, которая не указана ни в одном из существующих ответов. 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 это, и посмотрите на 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 для удовольствия. Но не стоит полагаться на них в вашем коде. Единственное безопасное правило:

  • Не пишите код, который предполагает два одинаковых, но созданных отдельно неизменных значения.
  • Не пишите код, в котором предполагается, что два одинаковых, но отдельно созданных неизменяемых значения различны.

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

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

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

РНР === Оператор, с другой стороны, описывается как проверка равенства и типа: 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 со всеми типами чисел, которые мы знаем, но это неизбежно будет неполным. Модуль types содержит список 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 изначально не было дерева типов с предикатами, подобными Scheme. number? или тип класса Хаскелла Num. is проверяет идентичность объекта, а не равенство значений. PHP также имеет красочную историю, где === по-видимому, ведет себя как is только на объекты в PHP5, но не на PHP4. Такова растущая боль при перемещении между языками (включая версии одного).

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

Компилятор теперь выдает SyntaxWarning при проверке личности (is а также is not) используются с определенными типами литералов (например, строки, целые числа). Часто они могут работать случайно в CPython, но не гарантируются спецификацией языка. Предупреждение рекомендует пользователям использовать тесты на равенство (== а также !=) вместо этого.

Это также происходит со строками:

>>> 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)

Теперь это неожиданно.

Посмотрите здесь

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

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