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

10 Solutions collect form web for “«Is» оператор ведет себя неожиданно с целыми числами”

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

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

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

проверяет, являются ли они одним и тем же объектом, а не только равными. Маленькие 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__ .

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

Вкратце – позвольте мне подчеркнуть: не следует использовать целые числа.

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

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

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

Так в чем же состоит прецедент? PEP8 описывает :

Сравнение с синглонами типа « None всегда должно выполняться с помощью или is not , а не с операторами равенства.

Вопрос

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

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

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

Это не ожидаемый результат. Почему это ожидалось? Это означает, что целые числа, оцененные в 256 ссылаются как a и b являются a и тем же экземпляром целого числа. Целые числа неизменны в 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 мог бы сделать это глобально, если бы он мог сделать это дешево (так как это будет стоить в поиске), возможно, другая реализация.

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

Что делает

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

 >>> a is b 

такой же как

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

Почему мы хотим использовать?

Это может быть очень быстрая проверка, например, проверка того, являются ли две очень длинные строки равными по стоимости. Но поскольку это относится к уникальности объекта, мы, таким образом, имеем ограниченные прецеденты. На самом деле мы в основном хотим использовать его для проверки None , который является singleton (единственный экземпляр, существующий в одном месте в памяти). Мы могли бы создать другие синглтоны, если есть потенциал для их объединения, с которыми мы можем проверить, но они относительно редки. Вот пример (будет работать в 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 и sentinel мы можем различать, когда bar вызывается без аргументов и когда он вызывается с None . Это первичные варианты использования – не использовать его для проверки равенства целых чисел, строк, кортежей или других подобных вещей.

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

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

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

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

В CPython функция C-API которая обрабатывает создание нового объекта intPyLong_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 -кода-haxxorz, но мы также не CHECK_SMALL_INT(ival); , мы можем видеть, что 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 идти, но продолжайте наше путешествие, глядя на 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) вы просто [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) 

Так будет возвращено True .


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

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

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

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

Оператор PHP === , с другой стороны, описывается как проверка равенства и тип: x == y and type(x) == type(y) в соответствии с комментарием Пауло Фрейтаса. Этого достаточно для общих чисел, но они отличаются от классов, которые определяют __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, встроенные числовые классы имеют номера базового класса. Номер, но он имеет ту же проблему:

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

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

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

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

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

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

  • Python - преобразовать элементы частичного подсети в int
  • Long Int literal - неверный синтаксис?
  • Как преобразовать int в шестнадцатеричную строку?
  • Преобразование списка строк в int (или удваивается) в Python
  • TypeError: Невозможно преобразовать объект 'int' в str неявно
  • перечислить список для цикла в 2D-матрице
  • Как преобразовать список строк в целое число в python
  • Преобразование массив байтов с переменным размером в целое / длинное
  • Python - лучший язык программирования в мире.