Оператор "is" ведет себя неожиданно с целыми числами - PullRequest
447 голосов
/ 20 ноября 2008

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

Ответы [ 10 ]

345 голосов
/ 20 ноября 2008

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

>>> 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 в этот случай не определен. : -)

91 голосов
/ 04 марта 2015

Оператор 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 имеет значение true тогда и только тогда, когда 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?

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

56 голосов
/ 20 ноября 2008

Это зависит от того, хотите ли вы увидеть, равны ли 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__.

41 голосов
/ 23 января 2016

Я опоздал, но вам нужен источник с вашим ответом? *

Хорошо, что в 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.


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

36 голосов
/ 20 ноября 2008

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

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

18 голосов
/ 20 ноября 2008

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

12 голосов
/ 21 ноября 2008

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

8 голосов
/ 20 марта 2013

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). Чуть менее абсурдным может быть использование объекта-таймера, значение которого будет отличаться при каждом использовании в качестве числа. Почему вы хотите эмулировать Now в Visual Basic вместо того, чтобы показывать, что это оценка с 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's number? или Haskell's class type Num . is проверяет идентичность объекта, а не равенство значений. PHP также имеет красочную историю, где ===, по-видимому, ведет себя как is только для объектов в PHP5, но не для PHP4 . Такова растущая боль при перемещении между языками (включая версии одного).

5 голосов
/ 25 марта 2018

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


С одной стороны, есть некоторые другие предварительно созданные значения, такие как пустые tuple, str и bytes, а также некоторые короткие строки (в CPython 3.6 это 256 односимвольных латинских 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 для удовольствия. Но не стоит полагаться на них в своем коде. Единственное безопасное правило:

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

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

4 голосов
/ 14 октября 2015

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

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

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

...