оператор " 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
EDIT: вот что я нашел в документации Python 2,"Обычный Integer Объекты" (Она же Python 3):
текущая реализация поддерживает массив целочисленных объектов, для все целых чисел от -5 до 256, когда вы создайте int в этом диапазоне вы на самом деле просто верните ссылку на существующий объект. Так и должно быть возможно изменение значения 1. Я подозреваю поведение питона в этот случай не определен. : -)
оператор "is" Python неожиданно ведет себя с целыми числами?
вкратце-позвольте мне подчеркнуть:не используйте
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, theid
это место в памяти, но это может быть какая-то другая уникальная идентификация номер в другой реализации. Чтобы подтвердить это с помощью кода:>>> 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
- do не используйте его для проверки равенства целых чисел, строк, кортежей или других подобных вещей.
это зависит от того, ищете ли вы, чтобы увидеть, если 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__
атрибуты.
Как вы можете увидеть в исходный файл intobject.c, Python кэширует небольшие целые числа для эффективности. Каждый раз, когда вы создаете ссылку на небольшое целое число, вы ссылаетесь на кэшированное небольшое целое число, а не на новый объект. 257-это не маленькое целое число, поэтому оно вычисляется как другой объект.
лучше использовать
==
для этой цели.
я опаздываю, но, вы хотите какой-то источник с вашим ответом?*
хорошая вещь о CPython является то, что вы можете увидеть источник для этого. Я собираюсь использовать ссылки для
3.5
отпустите сейчас; найти соответствующий2.x
- это тривиально.в CPython, the
C-API
функция, которая обрабатывает создание нового
Я думаю, что ваша гипотеза верна. Экспериментируйте с
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
рассматриваются как литералы и все, что выше рассматривается по-разному!
для объектов с неизменяемыми значениями, таких как ints, strings или datetimes, идентификатор объекта не особенно полезен. Лучше думать о равенстве. Идентичность по существу является деталью реализации для объектов value-поскольку они неизменяемы, нет никакой эффективной разницы между наличием нескольких ссылок на один и тот же объект или несколько объектов.
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). Чуть менее абсурдным может быть объект timer, который имеет разные значения каждый раз, когда он используется как число. Именно поэтому вы хотите эмулировать Visual Basic
Now
вместо того, чтобы показать, что это оценка сtime.time()
I не знаю.Грег Хьюгилл (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)
кстати, включает в себя произведет отдельные экземпляры низкого уровня числа.
Я не знаю ответа на этот вариант вопроса. Я полагаю, что теоретически можно использовать ctypes для вызова
PyNumber_Check
, но даже то, что функция обсуждается, и это, конечно, не портативный. Нам просто нужно быть менее разборчивыми в том, что мы сейчас тестируем.в конце концов, эта проблема связана с тем, что Python изначально не имеет дерева типов с предикатами, такими как схемы
number?
или Хаскелл класс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-это не единственный способ, которым это может произойти. Реализация Python никогда не гарантированный чтобы сделать это, но все они делают это не только для небольших ints.
во-первых, есть некоторые другие предварительно созданные значения, такие как пустой
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
это, и посмотрите наco_consts
-есть1
и2
, два(1, 2)
кортежи, которые разделяют то же самое1
и2
но не идентичны, и((1, 2), (1, 2))
Кортеж, который имеет две равные кортежи.
есть еще одна оптимизация, которую делает CPython: string interning. В отличие от компилятора constant folding, это не ограничивается литералами исходного кода:
>>> m = 'abc' >>> n = 'abc' >>> m is n True
С другой стороны, он ограничен
str
тип, и строки внутренняя память рода "компактную формате ASCII", "компактных", или "наследие готов", и во многих случаях только "ascii compact" будет интернирован.
во всяком случае, правила для того, какие значения должны быть, могут быть или не могут быть различными, варьируются от реализации к реализации, а также между версиями одной и той же реализации и, возможно, даже между запусками одного и того же кода на одной и той же копии одной и той же реализации.
это может быть стоит изучить правила для одного конкретного Python для удовольствия. Но не стоит полагаться на них в коде. Единственное безопасное правило:
- не пишите код, который предполагает два равных, но отдельно созданных неизменяемых значения идентичны.
- не пишите код, который предполагает два равных, но отдельно созданных неизменяемых значения различны.
или, другими словами, используйте только
is
для проверки документированных синглетов (напримерNone
) или только созданы в одном месте в коде (например,_sentinel = object()
фразеологизм).