Подтвердить что ты не робот

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

4b9b3361

Ответ 1

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

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

EDIT: вот что я нашел в документации Python 2, "Plain Integer Objects" (То же самое для Python 3):

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

Ответ 2

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

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

Ответ 3

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

is проверяет, являются ли они одним и тем же объектом, а не только равными. Небольшие ints, вероятно, указывают на одно и то же место памяти для обеспечения эффективности пространства

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

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

Ответ 4

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

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

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

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

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

Если вы не поработали с кодом 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.


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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

Оператор 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. Таковы растущие боли в перемещении по языкам (включая версии одного).

Ответ 9

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

Ответ 10

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

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

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

Ответ 11

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

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

Ответ 12

Python 3.8: https://docs.python.org/3.8/whatsnew/3.8.html#changes-in-python-behavior

Компилятор теперь производит SyntaxWarning, когда проверка идентичности (есть и не) используются с определенными типами литералов (например, строки, целые числа). Они часто могут работать случайно в CPython, но не гарантируются спецификация языка Предупреждение рекомендует пользователям использовать тесты на равенство (== и! =) вместо. (Внесено Сергеем Сторчака в bpo-34850.)