Почему следующие действия ведут себя неожиданно в 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
оператор может отличить их. Почему дырявая абстракция? Что может быть лучше для сравнения двух произвольных объектов, чтобы увидеть, являются ли они одинаковыми, когда я не знаю заранее, являются ли они числами или нет?
Ответы:
Взгляните на это:
Вот что я нашел в документации по Python 2 «Простые целочисленные объекты» (то же самое для Python 3 ):
источник
В заключение - позвольте мне подчеркнуть: не используйте
is
для сравнения целых чисел.Это не то поведение, о котором вы должны ожидать.
Вместо этого используйте
==
и!=
для сравнения на равенство и неравенство соответственно. Например:объяснение
Чтобы знать это, вам нужно знать следующее.
Во-первых, что делает
is
? Это оператор сравнения. Из документации :И поэтому следующие эквивалентны.
Из документации :
Обратите внимание, что тот факт, что id объекта в CPython (эталонная реализация Python) является местоположением в памяти, является деталью реализации. Другие реализации Python (такие как Jython или IronPython) могут легко иметь другую реализацию для
id
.Так для чего нужен вариант
is
? PEP8 описывает :Вопрос
Вы задаете и задаете следующий вопрос (с кодом):
Это не ожидаемый результат. Почему это ожидается? Это только означает, что целые числа, на которые
256
ссылаются оба,a
иb
являются одним и тем же экземпляром целого числа. Целые числа неизменны в Python, поэтому они не могут измениться. Это не должно иметь никакого влияния на любой код. Этого не следует ожидать. Это просто деталь реализации.Но, возможно, нам следует порадоваться, что в памяти нет нового отдельного экземпляра каждый раз, когда мы указываем значение, равное 256.
Похоже, теперь у нас есть два отдельных экземпляра целых чисел со значением
257
в памяти. Так как целые числа неизменны, это тратит впустую память. Будем надеяться, что мы не будем тратить на это много. Мы, вероятно, нет. Но такое поведение не гарантируется.Что ж, похоже, ваша конкретная реализация Python пытается проявить смекалку и не создает избыточные целые числа в памяти без необходимости. Похоже, вы указываете, что используете референтную реализацию Python, то есть CPython. Хорошо для CPython.
Возможно, было бы даже лучше, если бы CPython мог сделать это глобально, если бы он мог делать это дешево (как это будет стоить при поиске), возможно, могла бы быть другая реализация.
Но что касается влияния на код, вам не нужно заботиться о том, является ли целое число конкретным экземпляром целого числа. Вы должны только заботиться , что значение этого экземпляра есть, и вы бы использовать обычные операторы сравнения для этого, то есть
==
.Что
is
делаетis
проверяет, чтоid
два объекта совпадают. В CPythonid
это место в памяти, но это может быть какой-то другой уникально идентифицирующий номер в другой реализации. Чтобы повторить это с кодом:такой же как
Почему мы хотим использовать
is
тогда?Это может быть очень быстрая проверка, скажем, проверка, равны ли две очень длинные строки по значению. Но так как это относится к уникальности объекта, у нас, таким образом, есть ограниченные варианты использования для него. Фактически, мы в основном хотим использовать его для проверки
None
, который является единственным (единственный экземпляр, существующий в одном месте в памяти). Мы можем создать другие синглтоны, если есть возможность их сопоставить, с которыми мы могли бы проверитьis
, но они относительно редки. Вот пример (будет работать в Python 2 и 3), например:Какие отпечатки:
Итак, мы видим, с помощью
is
и дозорного, мы можем различать, когдаbar
вызывается без аргументов и когда вызывается сNone
. Это основные варианты использования дляis
- не используйте его для проверки на равенство целых чисел, строк, кортежей или других подобных вещей.источник
is
- не используйте его для проверки на равенство целых чисел, строк, кортежей или других подобных вещей». Тем не менее, я пытаюсь интегрировать простой конечный автомат в мой класс, и, поскольку состояния являются непрозрачными значениями, единственное наблюдаемое свойство которых состоит в том, что они идентичны или различны, для них вполне естественно быть сопоставимымиis
. Я планирую использовать интернированные строки в качестве состояний. Я бы предпочел простые целые числа, но, к сожалению, Python не может интернировать целые числа (0 is 0
это детали реализации).Это зависит от того, хотите ли вы увидеть, равны ли 2 вещи или один и тот же объект.
is
проверяет, являются ли они одним и тем же объектом, а не просто равным. Маленькие целые, вероятно, указывают на одну и ту же область памяти для экономии местаВы должны использовать,
==
чтобы сравнить равенство произвольных объектов. Вы можете указать поведение с помощью атрибутов__eq__
, and__ne__
.источник
Я опоздал, но вы хотите какой-нибудь источник с вашим ответом? Я попытаюсь сказать это во вступительной манере, чтобы больше людей могли следовать за ними.
Хорошая вещь о CPython в том, что вы можете увидеть источник этого. Я собираюсь использовать ссылки для версии 3.5 , но найти соответствующие 2.x тривиально.
В CPython функция C-API, которая обрабатывает создание нового
int
объектаPyLong_FromLong(long v)
. Описание этой функции:(Мой курсив)
Не знаю как вы, но я вижу это и думаю: давайте найдем этот массив!
Если вы не возились с кодом C, реализующим CPython, вам следует ; все довольно организовано и читабельно. Для нашего случая, мы должны смотреть в
Objects
подкаталоге из основного исходного кода дерева каталогов .PyLong_FromLong
имеет дело сlong
объектами, поэтому нетрудно понять, что нам нужно заглянуть внутрьlongobject.c
. Заглянув внутрь, вы можете подумать, что все хаотично; они, но не бойтесь, функция, которую мы ищем, охлаждает линию 230, ожидая, чтобы мы ее проверили. Это небольшая функция, поэтому основная часть (исключая объявления) легко вставляется сюда:Так вот, мы не мастер-код С-haxxorz, но мы также не глупы , мы можем видеть, что
CHECK_SMALL_INT(ival);
подглядывает на нас всех соблазнительно; мы можем понять, что это как-то связано с этим. Давайте проверим это:Так что это макрос, который вызывает функцию,
get_small_int
если значениеival
удовлетворяет условию:Так что же
NSMALLNEGINTS
иNSMALLPOSINTS
? Макросы! Вот они :Итак, наше условие это
if (-5 <= ival && ival < 257)
вызовget_small_int
.Далее давайте рассмотрим
get_small_int
во всей красе (ну, мы просто посмотрим на его тело, потому что именно там есть интересные вещи):Хорошо, объявляем a
PyObject
, утверждаем, что предыдущее условие выполняется, и выполняем присваивание:small_ints
выглядит очень похоже на тот массив, который мы искали, и это так! Мы могли бы просто прочитать эту чертову документацию, и мы бы все знали! :Итак, это наш парень. Когда вы захотите создать новый
int
в диапазоне,[NSMALLNEGINTS, NSMALLPOSINTS)
вы просто получите ссылку на уже существующий объект, который был предварительно выделен.Поскольку ссылка ссылается на один и тот же объект,
id()
прямая выдача или проверка идентичности сis
ним вернет точно то же самое.Но когда они распределяются ??
Во время инициализации в
_PyLong_Init
Python с удовольствием войдем в цикл for, сделайте это за вас:Проверьте источник, чтобы прочитать тело цикла!
Я надеюсь, что мое объяснение сделало вас ясными с вещами теперь (игра слов явно была намерена).
Но
257 is 257
? Что происходит?Это на самом деле легче объяснить, и я уже пытался это сделать ; это связано с тем, что Python выполнит этот интерактивный оператор как один блок:
Во время компиляции этого оператора CPython увидит, что у вас есть два совпадающих литерала, и вы будете использовать одно и то же
PyLongObject
представление257
. Вы можете увидеть это, если выполнили сборку самостоятельно и изучили ее содержимое:Когда CPython выполняет операцию, он просто собирается загрузить точно такой же объект:
Так
is
вернетсяTrue
.источник
Как вы можете проверить в исходном файле intobject.c , Python для эффективности кэширует маленькие целые числа. Каждый раз, когда вы создаете ссылку на маленькое целое число, вы ссылаетесь на кешированное маленькое целое число, а не на новый объект. 257 - не маленькое целое число, поэтому оно рассчитывается как другой объект.
Лучше использовать
==
для этой цели.источник
Я думаю, что ваши гипотезы верны. Эксперимент с
id
(личность объекта):Похоже, что числа
<= 255
трактуются как литералы, а все вышеперечисленное трактуется иначе!источник
Для объектов с неизменяемыми значениями, таких как int, строки или datetime, идентичность объекта не особенно полезна. Лучше подумать о равенстве. Идентичность - это, по сути, деталь реализации для объектов-значений - поскольку они неизменны, нет эффективной разницы между наличием нескольких ссылок на один и тот же объект или несколько объектов.
источник
Есть еще одна проблема, которая не указана ни в одном из существующих ответов. Python может объединять любые два неизменных значения, и заранее созданные небольшие значения int не единственный способ, которым это может произойти. Реализация Python никогда не гарантируется , но все они делают это не только для небольших целых.
С одной стороны, есть некоторые другие предварительно созданные значения, такие как пустые
tuple
,str
иbytes
, и некоторые короткие строки (в CPython 3.6 это 256 односимвольных строк Latin-1). Например:Но даже и не созданные ранее значения могут быть идентичными. Рассмотрим эти примеры:
И это не ограничивается
int
ценностями:Очевидно, что CPython не поставляется с предварительно созданным
float
значением для42.23e100
. Итак, что здесь происходит?Компилятор CPython сольются постоянные значения некоторых заведомо неизменных типов , таких как
int
,float
,str
,bytes
, в том же модуле компиляции. Для модуля весь модуль является модулем компиляции, но в интерактивном интерпретаторе каждый оператор является отдельным модулем компиляции. Посколькуc
иd
определены в отдельных операторах, их значения не объединяются. Посколькуe
иf
определены в одном и том же утверждении, их значения объединяются.Вы можете увидеть, что происходит, разобрав байт-код. Попробуйте определить функцию, которая делает это,
e, f = 128, 128
а затем вызватьdis.dis
ее, и вы увидите, что есть единственное постоянное значение(128, 128)
Вы можете заметить, что компилятор хранится
128
как константа, даже если он фактически не используется байт-кодом, что дает вам представление о том, как мало делает оптимизация компилятора CPython. Это означает, что (непустые) кортежи в действительности не сливаются:Положим , что в функции,
dis
она, и взгляд наco_consts
-Есть это1
и А2
, два(1, 2)
кортежи , которые разделяют то же самое1
и ,2
но не идентичны, и((1, 2), (1, 2))
кортеж , который имеет два различных равных кортежи.Есть еще одна оптимизация, которую делает CPython: интернирование строк. В отличие от свертывания констант компилятора, это не ограничивается литералами исходного кода:
С другой стороны, он ограничен
str
типом и строками внутренней памяти типа «ascii compact», «compact» или «legacy ready» , и во многих случаях интернируется только «ascii compact».В любом случае, правила для того, какие значения должны быть, могут быть или не различаться, различаются в разных реализациях и в разных версиях одной и той же реализации, а может быть, даже между запусками одного и того же кода в одной и той же копии одной и той же реализации ,
Может быть стоит изучить правила для одного конкретного Python для удовольствия. Но не стоит полагаться на них в своем коде. Единственное безопасное правило:
x is y
, используйтеx == y
)x is not y
, используйтеx != y
)Или, другими словами, используйте только
is
для проверки документированных синглетов (напримерNone
) или которые создаются только в одном месте кода (например,_sentinel = object()
идиома).источник
x is y
для сравнения, используйтеx == y
. Также не используйтеx is not y
, используйтеx != y
a=257; b=257
на одной строкеa is b
Trueis
является оператором равенства идентичности (функционирующим какid(a) == id(b)
); просто два равных числа не обязательно являются одним и тем же объектом. Из соображений производительности некоторые небольшие целые числа запоминаются, поэтому они будут одинаковыми (это можно сделать, поскольку они неизменяемы).===
Оператор PHP , с другой стороны, описывается как проверка равенства и типа:x == y and type(x) == type(y)
согласно комментарию Пауло Фрейтаса. Это будет достаточно для простых чисел, но отличается отis
классов, которые определяют__eq__
абсурдным образом:PHP, по-видимому, допускает то же самое для «встроенных» классов (которые я имею в виду, реализованные на уровне C, а не в PHP). Чуть менее абсурдным может быть использование объекта-таймера, значение которого будет отличаться при каждом использовании в качестве числа. Почему вы хотите эмулировать Visual Basic
Now
вместо того, чтобы показывать, что это оценка, с которойtime.time()
я не знаю.Грег Хьюгилл (OP) сделал один уточняющий комментарий: «Моя цель - сравнить идентичность объекта, а не равенство значений. За исключением чисел, где я хочу трактовать идентичность объекта так же, как равенство значений».
Это даст еще один ответ, поскольку мы должны классифицировать вещи как числа или нет, чтобы выбрать, сравнивать ли мы с
==
илиis
. CPython определяет протокол нумерации , включая PyNumber_Check, но он недоступен из самого Python.Мы можем попытаться использовать
isinstance
все известные нам типы чисел, но это неизбежно будет неполным. Модуль types содержит список StringTypes, но не содержит NumberTypes. Начиная с Python 2.6, встроенные числовые классы имеют базовый классnumbers.Number
, но у него есть та же проблема:Кстати, NumPy будет выдавать отдельные экземпляры низких чисел.
На самом деле я не знаю ответа на этот вариант вопроса. Я полагаю, теоретически можно использовать ctypes для вызова
PyNumber_Check
, но даже эта функция обсуждалась , и она, безусловно, не переносима. Нам просто нужно быть менее внимательным к тому, что мы сейчас тестируем.В конце концов, эта проблема связана с тем, что в Python изначально не было дерева типов с предикатами типа Scheme
number?
или класса типов Haskell Num . проверяет идентичность объекта, а не равенство значений. PHP также имеет красочную историю, где, по- видимому, ведет себя как только на объектах в PHP5, но не PHP4 . Такова растущая боль при перемещении между языками (включая версии одного).is
===
is
источник
Это также происходит со строками:
Теперь все вроде нормально.
Это тоже ожидается.
Теперь это неожиданно.
источник
'xx'
является ожидаемой, как есть'xxx'
, но'x x'
не является.xx
в вашем сеансе Python есть что-нибудь названное , эта строка уже интернирована; и может быть эвристика, которая делает это, если это просто напоминает имя. Как и с числами, это можно сделать, потому что они неизменны. docs.python.org/2/library/functions.html#intern guilload.com/python-string-interningЧто нового в Python 3.8: Изменения в поведении Python :
источник