Возвращает false, если переменные указывают на один и тот же объект памяти

Обновлено: 21.11.2024

Нам часто нужно сравнить данные двух переменных или объектов или ссылки двух объектов в Kotlin. Это вызывает еще один вопрос, какую проверку на равенство мы должны использовать в каком случае. Давайте разберемся, какие типы проверок доступны в Kotlin.

Структурное равенство (‘==’)

Оператор

== используется для сравнения данных двух переменных. Пожалуйста, не путайте этот оператор равенства с оператором Java ==, так как оба они разные. Оператор == в Kotlin сравнивает только данные или переменные, тогда как в Java или других языках == обычно используется для сравнения ссылок. Отрицательным аналогом == в Kotlin является !=, который используется для сравнения, если оба значения не равны друг другу.

Ссылочное равенство ('===')

Оператор

=== используется для сравнения ссылок двух переменных или объектов. Это будет верно только в том случае, если оба объекта или переменные указывают на один и тот же объект. Отрицательным аналогом === в Kotlin является !==, который используется для сравнения, если оба значения не равны друг другу. Для значений, которые во время выполнения представляются как примитивные типы (например, Int), проверка на равенство === эквивалентна проверке ==.

метод .equals

Метод

equals(other: Any?) реализован в классе Any и может быть переопределен в любом расширяющемся классе. Метод .equals также сравнивает содержимое переменных или объектов точно так же, как оператор ==, но он ведет себя по-разному в случае сравнения с плавающей запятой и двойного сравнения. Разница между == и .equals заключается в сравнении с числами с плавающей запятой и двойным сравнением, .equals не соответствует стандарту IEEE 754 для арифметики с плавающей запятой.

И что означает несоответствие стандарту IEEE 754 для арифметики с плавающей запятой?

Запутались?

Поясню это и все на примерах. Сначала сравним две переменные примитивного типа Int по всем проверкам на равенство.

Все приведенные выше сравнения будут печатать true, потому что примитивный тип данных проверяет значение только в случае ===, которое также будет равно в нашем случае. Теперь воспользуемся классом-оболочкой вместо типа данных Primitive и сравним все три проверки на равенство

В приведенном выше случае == и .equals выводит true, потому что они сравнивают только значения, тогда как === сравнивает ссылки на разные объекты, поэтому выводит false . Теперь давайте рассмотрим другой случай, когда мы создали собственный объект пользовательского класса и сравнили его со всеми тремя проверками.

Причина приведенного выше сравнения очевидна. Поскольку Empoyee не является примитивным типом данных или классом-оболочкой, все три сравнили ссылки, которые вернули false для всех трех проверок. Но в случае сравнения строк if проверяется только содержимое строки, которая была равна, поэтому она возвращает true для каждого случая.

Подождите, но вы сказали == и .equals сравнивает только содержимое объекта, которое в нашем случае было равным.

Именно. Но сравнение содержимого работает только в том случае, если это класс данных. Если это обычный класс, компилятор рассматривает оба объекта как разные объекты, даже если содержимое одинаковое, но если это класс данных, компилятор сравнивает данные и возвращает true, если содержимое одинаковое. Давайте изменим класс выше на класс данных.

Как и в случае с плавающим и двойным сравнением, .equals не соответствует стандарту IEEE 754 для арифметики с плавающей запятой и возвращает false, когда -0,0 сравнивается с 0,0, тогда как == и === возвращают true .

В Python разница между оператором is и оператором == заключается в следующем:

  1. Инструкция is проверяет, ссылаются ли два объекта на один и тот же объект.
  2. Оператор == проверяет, имеют ли два объекта одинаковое значение.

Переменные a и b являются разными объектами, даже если они имеют одинаковое значение. Таким образом, сравнение значений с помощью оператора == возвращает True, но проверка того, ссылаются ли переменные на один и тот же объект, дает False.

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

Оглавление

Идентификация объекта в Python

В Python наличие двух объектов с одинаковым значением не означает, что объекты будут идентичными. Подобно тому, как в реальной жизни два человека с одинаковым именем не означают, что это один и тот же человек.

Всякий раз, когда вы создаете объект Python, Python сохраняет его в памяти по определенному адресу памяти. Каждый объект получает свой уникальный адрес в памяти.

Вы можете проверить адрес памяти любого объекта Python, используя встроенную функцию id(). Он возвращает целочисленное значение, представляющее адрес памяти объекта.

Теперь, если вы создадите два объекта с одинаковым значением, они все равно окажутся в разных местах памяти.Вы можете убедиться в этом, проверив идентификаторы объектов.

Здесь вы можете видеть, что идентификаторы разные.

В Python есть встроенный механизм проверки совпадения идентификаторов двух объектов. Это утверждение.

Утверждение is в Python

Выражение is в Python проверяет, идентичны ли два объекта. Другими словами, он проверяет, находятся ли два объекта в одном и том же адресе памяти, то есть имеют ли объекты одинаковые идентификаторы.

Инструкция is возвращает True, если объекты имеют одинаковый идентификатор, иначе False.

С помощью оператора is вы можете заменить этот фрагмент кода:

В примере из предыдущей главы вы можете заменить:

Теперь вы уже понимаете, в чем разница между оператором равенства == и оператором is в Python. Далее давайте посмотрим, как переменные являются просто псевдонимами объектов за кулисами, и как это влияет на идентичность переменных.

Переменные — это псевдонимы в Python

Переменную Python можно рассматривать как имя, прикрепленное к объекту. Объект Python может иметь много переменных, которые ссылаются на один и тот же объект. Таким образом, каждая переменная похожа на псевдоним скрытого объекта.

Давайте посмотрим, что на самом деле означает присвоение переменной в Python и как оно связано с идентификаторами объектов.

Ссылки на объекты Python

Взгляните на этот фрагмент кода:

При запуске интерпретатор Python

  1. Создает целочисленный объект.
  2. Присваивает ему значение 1000.
  3. Показывает значение 1000 в консоли.

Но после этого у вас больше не будет доступа к этому целочисленному объекту. Становится сиротой. Однако вы можете «сохранить» этот объект в переменной.

Но почему слово «магазин» в кавычках?

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

Чтобы продемонстрировать это, давайте создадим переменную, в которой хранится целое число:

Этот фрагмент кода работает следующим образом:

  1. Создает целочисленный объект.
  2. Присваивает объекту значение 1000.
  3. Создает псевдоним num, который можно использовать для ссылки на новый целочисленный объект.

Поэтому переменная num не хранит целочисленный объект. Он указывает только на адрес памяти этого объекта.

Вот как это выглядит:

Теперь всякий раз, когда вы обращаетесь к переменной num в своем коде, Python заменяет ее объектом int, представляющим 1000.

Пример. Давайте создадим две переменные списка так, чтобы вторая переменная была установлена ​​равной первой:

Затем изменим первое число списка a на 1000 и проверим содержимое списков:

Подожди минутку! Изменение значения списка a также изменило значение списка b. Почему это происходит?

Как вы уже узнали, переменная — это указатель на место в памяти, где на самом деле находится объект. В приведенном выше примере вы сначала создаете переменную a, указывающую на список:

Затем вы создаете новую переменную b, которая указывает на переменную a:

Как вы знаете, когда вы вызываете переменную, вы получаете объект, на который указывает переменная. Таким образом, новая переменная b становится псевдонимом объекта, на который ссылается a.

Другими словами, теперь a и b указывают на один и тот же объект по одному и тому же адресу памяти. Таким образом, если вы измените список, изменятся и a, и b.

Вы можете убедиться, что объекты указывают на один и тот же адрес памяти, используя оператор is:

Теперь вы понимаете, что переменные Python — это просто ссылки на реальные объекты.

Для лучшего понимания рассмотрим еще один пример. На этот раз вместо работы со списком объектов давайте создадим целочисленный объект, на который ссылаются переменные a и b.

Теперь давайте изменим значение в a:

Теперь давайте посмотрим, как выглядят переменные a и b:

Они разные! Переменные a и b указывают на одну и ту же ячейку памяти, так почему же b не изменилась при изменении a?

Причина, по которой это происходит, заключается в том, что вы на самом деле не обновляете исходный целочисленный объект. Вместо этого вы создаете совершенно новый целочисленный объект, который присваиваете переменной a.

Как вы помните, присваивание переменной:

Сообщает интерпретатору Python:

  1. Создать новый целочисленный объект по новому адресу памяти.
  2. Укажите значение 2000.
  3. Разрешить вызов объекта с именем a.

Другими словами, присвоение 2000 переменной a делает ее ссылкой на новый целочисленный объект, который находится в другом месте памяти. С другой стороны, переменная b по-прежнему указывает на объект, на который ранее указывала переменная a.

Вы можете убедиться, что переменные указывают на разные объекты, используя оператор is:

Кстати, целое число — это неизменяемый объект в Python. Этот пример хорошо это демонстрирует. Невозможно изменить существующий целочисленный объект. Вместо этого вы всегда создаете новый объект, чтобы «изменить» значение исходного объекта.

Исключения для идентификации

На данный момент вы знаете, что присваивание переменной в Python создает ссылку на объект.

Имея это в виду, вы не удивитесь:

Здесь переменные a и b относятся к разным объектам в памяти.

Но что может быть удивительным, так это то, что при повторении этого эксперимента с меньшими значениями идентичности остаются теми же:

Так почему же это происходит?

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

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

Если вы создаете целое число за пределами диапазона [-5, 256], вы всегда создаете новый целочисленный объект.

Это приводит к несоответствиям при использовании оператора is вместо ==:

Здесь a и b относятся к одному и тому же адресу в памяти из-за оптимизации, описанной выше. С другой стороны, значения x и y не оптимизированы и поэтому указывают на разные адреса памяти.

Напомним, никогда не используйте оператор is для сравнения двух переменных, значения которых должны быть равны!

При использовании «==» и при использовании «is»

В большинстве случаев при сравнении в Python следует использовать ==.

Основное практическое правило:

  • Используйте ==, чтобы проверить, имеют ли два объекта одинаковое значение.
  • Используйте оператор is, чтобы проверить, ссылаются ли две переменные на один и тот же объект.

Давайте рассмотрим несколько примеров.

Пример равного значения

При сравнении целых чисел, строк, списков, наборов, словарей или других настраиваемых изменяемых объектов используйте оператор равенства ==.

Нет Пример

Рекомендуется использовать оператор is, если вы сравниваете что-то с None. Не используйте оператор равенства ==.

Это также рекомендуется в PEP8, официальном руководстве по стилю для Python:

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

PEP8

Это связано с тем, что в пользовательские классы можно записывать методы, которые обрабатывают == None не так, как вы ожидаете.

Как видите, сравнение массива нулей с None с использованием оператора равенства дает массив логических значений. Однако сравнение массива с None с помощью оператора is дает ожидаемые результаты.

Пример экземпляра класса

Обычно использование оператора is полезно, когда вы хотите сравнить объект с чем-то, что должно существовать в памяти только один раз.

Например, сравнение экземпляров класса может быть более разумным с помощью оператора is. Это связано с тем, что вам может потребоваться убедиться, что каждый экземпляр класса в программе уникален.

Но почему бы в таком случае не использовать оператор ==?

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

Например, предположим, что у вас есть класс User, в котором вы можете сравнивать пользователей по их именам. Если имена двух пользователей совпадают, оператор == возвращает значение True. Для этого вам нужно переопределить специальный метод __eq__(), который определяет, что происходит при вызове == между двумя объектами.

Вот код:

Теперь вы можете проверить, имеют ли два пользователя одинаковые имена, используя оператор равенства:

Однако теперь невозможно проверить, есть ли две переменные, указывающие на один и тот же пользовательский объект. Это плохо, потому что вы хотите гарантировать, что каждый пользователь в программе уникален, и никакие два пользователя не ссылаются на один и тот же объект.

Чтобы решить эту проблему, используйте, чтобы проверить, совпадают ли пользователи:

Как видите, несмотря на то, что имена пользователей одинаковы, оператор is понимает, что эти переменные относятся к разным объектам пользователей.

Итак, в данном случае использование оператора is дает более надежные результаты, чем оператор равенства ==. Используя оператор is, вы можете надежно убедиться, что в программе есть только один user1.

Заключение

Сегодня вы узнали, в чем разница между оператором is и оператором равенства == в Python.

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

Операция равенства == проверяет, имеют ли два объекта одинаковое значение. Но оператор равенства не заботится о том, являются ли объекты на самом деле одним и тем же объектом с одинаковыми идентификаторами.

Вывод: в этом руководстве вы узнаете об операторе is в Python и о различиях между оператором is и оператором равенства ( == ).

Введение в оператор Python is

Оператор Python сравнивает две переменные и возвращает значение True, если они ссылаются на один и тот же объект. Если две переменные ссылаются на разные объекты, оператор is возвращает False .

Другими словами, оператор is сравнивает идентификаторы двух переменных и возвращает значение True, если они ссылаются на один и тот же объект.

Давайте рассмотрим следующий пример:

  • Сначала определите переменную a, которая ссылается на объект типа int со значением 100 .
  • Во-вторых, определите другую переменную b, которая ссылается на тот же объект, на который ссылается переменная a.
  • В-третьих, используйте оператор is, чтобы проверить, ссылаются ли a и b на один и тот же объект, и отобразить результат.

Поскольку и a, и b ссылаются на один и тот же объект, результатом будет True .

В следующем примере определяются две переменные a и b и инициализируются значением 100:

В этом примере нет связи между a и b . Однако, когда вы присваиваете b значение 100, Python Memory Manager повторно использует существующий объект. Следовательно, и a, и b ссылаются на один и тот же объект:

Обратите внимание, что результат этого примера может отличаться в зависимости от того, как реализован диспетчер памяти Python. И не стоит на это рассчитывать.

В следующем примере определяются два списка с одинаковыми элементами и используется оператор is, чтобы проверить, ссылаются ли они на один и тот же объект списка:

В этом примере списки являются изменяемыми объектами. Python Memory Manager не использует повторно существующий список, а создает новый в памяти. Поэтому переменные рангов и рейтингов ссылаются на разные списки:

Python — это оператор, а не оператор ==

Операция равенства ( == ) сравнивает две переменные на равенство и возвращает True, если они равны. В противном случае возвращается False .

В следующем примере используется оператор is и оператор ==:

Поскольку a и b ссылаются на один и тот же объект, они идентичны и равны.

В следующем примере оба списка содержат одни и те же элементы, поэтому они равны.

Однако, поскольку они ссылаются на разные объекты списка в памяти, они не идентичны:

Python не является оператором

Чтобы отменить оператор is, используйте оператор not. Оператор is not возвращает False, если две переменные ссылаются на один и тот же объект. В противном случае возвращается True .

В следующем примере используется оператор is not, чтобы проверить, не ссылаются ли две переменные на один и тот же объект списка:

Как проверить, верно ли что-то в Python? Есть три способа:

  • Один плохой способ: if variable == True:
  • Еще один плохой способ: если переменная имеет значение True:
  • И хороший способ, рекомендуемый даже в рекомендациях по программированию PEP8: если переменная:

«Плохие» способы не только осуждаются, но и медленнее. Давайте проведем простой тест:

Использование is примерно на 60% медленнее, чем if variable (17,4/10,9≈1,596), но использование == медленнее на 120% (24,9/10,9≈2,284)! Неважно, действительно ли переменная имеет значение True или False — различия в производительности схожи (если переменная имеет значение True, все три сценария будут немного медленнее).

Аналогичным образом мы можем проверить, не является ли переменная истинной, используя один из следующих методов:

  • if variable != True: ("плохо")
  • если значение переменной не равно True: ("плохо")
  • если не переменная: (хорошо)

если не побеждает переменная. is not на 50% медленнее (18,8/12,4≈1,516) и != занимает вдвое больше времени (26/12,4≈2,016).

Версии с переменной if и без переменной быстрее выполняются и быстрее читаются.Это распространенные идиомы, которые вы часто встретите в Python (или других языках программирования).

О серии статей "Написание на Python быстрее"

"Writing Faster Python" – это серия коротких статей, в которых обсуждаются способы решения некоторых распространенных проблем с помощью различных структур кода. Я запускаю несколько тестов, обсуждаю разницу между каждым фрагментом кода и заканчиваю некоторыми личными рекомендациями.

Смогут ли эти рекомендации сделать ваш код намного быстрее? Не совсем.
Сможет ли знание этих небольших различий сделать программиста на Python немного лучше? Надеюсь!

Подробнее о некоторых сделанных мной предположениях, настройке сравнительного анализа и ответах на некоторые распространенные вопросы можно прочитать во вводной статье.

«правда» и «ложь»

Почему я всегда беру плохой в кавычки? Это потому, что способ "плохой" не всегда плохой (он неправильный только тогда, когда вы хотите сравнить логические значения, как указано в PEP8). Иногда вы намеренно должны использовать одно из этих других сравнений.

В Python (и многих других языках) есть значения True и истинные значения. То есть значения интерпретируются как True, если вы запускаете bool(variable) . Точно так же есть False и значения falsy (значения, которые возвращают False из bool(variable) ). Пустой список ( [] ), строка ( "" ), словарь ( <> ), None и 0 являются ложными, но они не являются строго ложными .

Иногда необходимо различать значения True/False и truethy/falsy. Если ваш код должен вести себя одним образом при передаче пустого списка, а другим — при передаче False, вы не можете использовать if not value.

Рассмотрите следующий сценарий:

У нас есть функция для обработки некоторых заказов. Если заказов нет, мы хотим вернуться, ничего не делая. В противном случае мы хотим обработать существующие заказы.

Мы предполагаем, что если ордеров нет, то для параметра ордеров установлено значение None . Но, если заказы - это пустой список, мы также возвращаемся без каких-либо действий! А может получиться пустой список из-за того, что кто-то просто обновляет платежную информацию прошлого заказа? Или, возможно, наличие пустого списка означает, что в системе есть ошибка. Мы должны отловить эту ошибку до того, как мы заполним базу данных пустыми заказами! Неважно, по какой причине список пуст, приведенный выше код проигнорирует его. Мы можем это исправить, более внимательно изучив параметр ордеров:

То же самое относится и к истинным значениям. Если ваш код должен работать иначе для True, чем, скажем, для значения 1, мы не можем использовать переменную if. Мы должны использовать == для сравнения числа (если переменная == 1) и is для сравнения с True (если переменная имеет значение True). Звучит запутанно? Давайте посмотрим на разницу между is и == .

is проверяет подлинность, == проверяет значение

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

Операция == сравнивает значения. Он проверяет, равно ли значение одной переменной значению какой-либо другой переменной.

Некоторые объекты в Python уникальны, например None , True или False . Каждый раз, когда вы присваиваете переменной значение True, она указывает на тот же объект True, что и другие переменные, которым присвоено значение True. Но каждый раз, когда вы создаете новый список, Python создает новый объект:

Важно знать разницу между is и == . Если вы думаете, что они работают одинаково, вы можете столкнуться со странными ошибками в своем коде:

В приведенном выше примере первый блок кода напечатает «да», а второй — нет. Это потому, что Python выполняет некоторые крошечные оптимизации, а небольшие целые числа имеют один и тот же идентификатор (они указывают на один и тот же объект). Каждый раз, когда вы присваиваете 1 новой переменной, она указывает на один и тот же объект 1. Но когда вы присваиваете 1000 переменной, она создает новый объект. Если мы используем b == 1000 , то все будет работать как положено.

Выводы

  • Чтобы проверить, равна ли переменная True/False (и вам не нужно различать значения True/False и значения truthy/falsy), используйте if переменная или, если не переменная. Это самый простой и быстрый способ сделать это.
  • Если вы хотите проверить, является ли переменная явно истинной или ложной (и не является ли она истинной/ложной), используйте is ( if variable имеет значение True ).
  • Если вы хотите проверить, равна ли переменная 0 или пуст ли список, используйте if variable == 0 или if variable == [] .

Привет, я Себастьян. Я пишу и рассказываю о Python: как писать лучший код, какие классные инструменты и библиотеки я использую, какие советы и рекомендации облегчают мне жизнь.

Посмотрите мою последнюю серию статей под названием «Написание более быстрого Python», в которой я сравниваю различные структуры кода и даю незапрошенные советы о том, когда их использовать. А если вам нравятся приложения для MacOS и инструменты CLI (а кому нет?), посмотрите мои любимые приложения для Mac и инструменты CLI.

Когда я не веду блог, я помогаю компаниям максимально эффективно использовать Python — либо на своих семинарах, либо в качестве консультанта/фрилансера.

Как 5-минутный взлом функции %reload превратился в кроличью нору различных инструментов и методов Python.

В чем разница между методами type() и isinstance() и какой из них лучше подходит для проверки типа объекта?

Введение в серию "Writing Faster Python". О чем речь, как проводить сравнительные тесты, часто задаваемые вопросы и дополнительные ресурсы.

Читайте также: