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

Обновлено: 19.05.2024

Я новичок в Python, и я должен сказать, как Python рассматривает аргументы назначения переменных и функции очень сбивает с толку. Вот что я не понимаю. Если я определяю две строки с явно тем же содержимым, что и "abc", то они фактически являются одним и тем же объектом, как показано ниже.

Это заставляет меня думать, как Python знает, что они одинаковы. Сравнивая литералы в коде? Если между x = 'abc' и y = 'abc' существует миллион разных вещей, Python возвращается полностью и говорит, что существует объект 'abc', так что я не собираюсь создавать новый 'abc'?

Я задавался вопросом, что произойдет, если я сделаю то же самое, но с очень длинной и сложной строкой. Это то, что произошло.

Теперь вы можете понять, почему я смущен. Итак, Python проверяет только "простые" строки, но не длинные строки при создании новых строковых объектов? Тогда как сложный/длинный слишком сложный/длинный?

Существует различие в x is y и x==y
x is y будет проверять, указывают ли x и y на один и тот же объект в куче или нет.
Пока x == y проверяет, является ли значение x и y одинаковым или нет.

Теперь давайте посмотрим, почему вы получили два разных результата

Если длина значения мала (максимум от 3 до 4 цифр), то только python проверяет, есть ли другой объект в куче с таким же значением или нет. Если присутствует, то он не создает новый объект, а если нет, он создает новый объект.
Если длина значения Big (более 4 цифр), python создает новый объект, он не проверяет, присутствует ли объект с тем же значением или нет.

Когда длина строки, int, float невелика

В python, когда two variables имеют same string,int or float value и if the length of value is small то обе переменные указывают на один и тот же объект, т.е. В куче памяти хранится только один объект.
попробуйте сами, взяв этот пример.

Здесь a is b проверяет, ссылаются ли a и b (т.е. указывая) на тот же объект в кучу или нет.
Поскольку 10 имеет длину 2, которая является небольшим интерпретатором python, будет создавать значение объекта 10 только один раз и то, что a и b будут ссылаться на один и тот же объект, а output is True

И a == b проверяет, равна ли значение a значению b.
Так как значение a равно 10, а значение b также равно 10, поэтому output is True

Вы также можете попробовать со строковым значением, например

Когда длина строки, int, float большая

Теперь, когда длина строки, int, float большая, тогда интерпретатор python не проверяет, есть ли объект с таким же значением в куче, он напрямую создает новый объект, даже если существовал объект с одинаковым значением

Вы можете попробовать это, например, также

Почему Python делает это

Python делает это для сокращения времени интерпретации (например, времени выполнения для кода). Если python продолжает проверять, присутствует ли эта длинная строка (например, длина которой равна 10) в куче или нет. Это потребует больше времени, потому что будет сравнивать буквенное обозначение со всеми объектами. Сравнивая 10 цифр, потребуется много времени.
Если длина строки меньше 4, то ее легко сравнить (потому что для сравнения требуется только 3 буквы), и это не займет много времени.

Объекты в Java можно сравнивать как по ссылке, так и по значению.

Сравнение ссылок

Если две переменные указывают на один и тот же объект в памяти, то ссылки, которые хранятся в этих переменных равны. Если сравнить такие переменные с помощью оператора равенства == , вы получите true, что логично. Тут все просто.

Код Вывод на экран

Сравнение по значению

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

Для определения идентичности разных объектов нужно использовать метод equals() Пример:

Код Вывод на экран

Метод equals есть не только у класса String — он есть у вообще всех классов.

Даже у тех, которые вы только будете писать, и вот почему.

В методе main объявлены переменные типа String. В консоли выводится результат их сравнения: если строки равны, выводится true, иначе — false. Тебе нужно раскомментировать одну строку, чтобы получился следующий вывод: true true Тело метода main менять нельзя: можно только раскомментировать одну стр

2. Класс Object

Все классы в Java считаются унаследованными от класса Object . Это создатели Java так придумали.

А если некий класс унаследован от класса Object , в этом классе-наследнике появляются все методы класса Object . Это и есть главный эффект наследования.

Другими словами, у каждого класса, даже если это не написано в его коде, есть все методы, которые есть у класса Object .

А среди таких методов есть методы, которые имеют отношение к сравнению объектов. Это метод equals() и метод hashCode() .

Код Как будет на самом деле:

В примере выше мы создали простой класс Person с параметрами name и age, без единого метода. Однако, т.к. все классы считаются унаследованными от класса Object , у класса Person скрытно появились два метода:

Метод Описание
Сравнивает текущий объект и переданный объект
Возвращает hash-code текущего объекта

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

Код Вывод на экран

3. Метод equals()

Унаследованный от класса Object метод equals() содержит самый простой алгоритм сравнивания текущего и переданного объектов — он просто сравнивает их ссылки.

Тот же эффект вы получите, если просто сравните переменные класса Person вместо вызова метода equals(). Пример:

Код Вывод на экран

Метод equals просто сравнивает у себя внутри ссылки a и b .

Однако у класса String сравнение работает по-другому. Почему?

Потому что разработчики класса String написали собственную реализацию метода equals() .

Реализация метода equals()

Давайте и мы напишем свою реализацию метода equals в классе Person . Разберем 4 основных случая.

Вне зависимости от того, для какого класса переопределять метод equals , он всегда принимает параметр типа Object

Сценарий 1: в метод equals передали тот же самый объект, у которого вызвали метод equals . Если ссылки у текущего и переданного объектов равны, нужно вернуть true . Объект совпадает сам с собой.

В коде это будет выглядеть так:

Сценарий 2: в метод equals передали ссылку null — сравнивать не с чем. Объект, у которого вызвали метод equals , точно не null, значит, в этом случае нужно вернуть false .

В коде это будет выглядеть так:

Сценарий 3: в метод equals передали ссылку на объект вообще не класса Person . Равен ли объект класса Person объекту класса не- Person ? Тут уже решает сам разработчик класса Person — как хочет, так и сделает.

Но обычно все же объекты считаются равными, если это объекты одного класса. Поэтому если в наш метод equals передали объект не класса Person , мы будем всегда возвращать false . А как проверить, какого типа объект? Правильно: с помощью оператора instanceof .

Вот как будет выглядеть наш новый код:

Переданный объект — null ?

4. Сравнение двух объектов Person

Что мы получили в итоге? Если мы дошли до конца метода, значит, у нас объект типа Person и ссылка не null . Тогда преобразовываем его к типу Person и будем сравнивать внутренности обоих объектов. Это и есть наш сценарий номер 4.

Переданный объект — null ?

Если переданный объект не типа Person

А как сравнивать два объекта Person ? Они равны, если у них равны имена ( name ) и возраст ( age ). Итоговый код будет выглядеть так:

Переданный объект — null ?

Если переданный объект не типа Person

Но и это еще не все.

Во-первых, поле name имеет тип String , а значит, поля name нужно сравнивать с помощью вызова метода equals .

Во-вторых, поле name вполне себе может быть равным null : тогда вызвать метод equals у него нельзя. Нужна дополнительная проверка на null :

Однако если name равно null в обоих объектах Person , имена все-таки равны.

Код четвертого сценария может выглядеть, например, так:

Если возрасты не равны,
сразу return false

Если this.name равно null , нет смысла сравнивать через equals . Тут либо второе поле name равно null , либо нет.

В методе main создаются два айфона с одинаковыми параметрами. В консоли выводится результат их сравнения. Разберись, почему сейчас результат отрицательный и сделай так, чтобы он был положительным. Для этого тебе нужно переопределить метод equals(Iphone), который будет учитывать все параметры. У двух

5. Метод hashCode()

Кроме метода equals , который выполняет детальное сравнение всех полей обоих объектов, есть еще один метод, который может использоваться для неточного, но очень быстрого сравнения — hashCode() .

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

Однако его можно ускорить. Допустим, слова начинаются на разные буквы: сразу же понятно, что они разные. Вот если они начинаются на одинаковые буквы, тогда гарантий нет: в дальнейшем слова могут оказаться и равными, и различными.

Метод hashCode() работает по похожему принципу. Если его вызвать у объекта, то он вернет некое число — аналог первой буквы в слове. Это число обладает такими свойствами:

  • У одинаковых объектов всегда одинаковые hash-code
  • У разных объектов могут быть одинаковые hash-code, а могут быть разные
  • Если у объектов разные hash-code, объекты точно разные

Для большего понимания перепишем эти свойства относительно слов:

  • У одинаковых слов всегда одинаковые первые буквы
  • У разных слов могут быть одинаковые первые буквы, а могут быть и разные
  • Если у слов разные первые буквы, слова точно разные

Последнее свойство и используется для ускоренного сравнения объектов:

Сначала у двух объектов вычисляются hash-code. Если эти hash-code разные, то объекты точно разные, и сравнивать их дальше не нужно.

А вот если hash-code одинаковые, придется все же сравнивать объекты с помощью equals.

Python считается одним из самых удивительных языков программирования. Многие люди выбирают его в качестве первого языка из-за его элегантности и простоты. Благодаря широкому сообществу, избытку пакетов и согласованности синтаксиса, опытные профессионалы также используют Python. Тем не менее, существует одна вещь, которая раздражает как новичков, так и некоторых профессиональных разработчиков – объекты Python.

Изменяемые vs. неизменяемые объекты

Функции id() и type()

Разобраться с изменяемостью типов данных нам помогут встроенные функции и операторы Python.

Встроенный метод id() возвращает идентификатор объекта в виде целого числа. Это целое число обычно относится к месту хранения объекта в памяти. Встроенная функция type() возвращает тип объекта.

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

Если сравнить две переменные, x и y , имеющие одинаковое значение, при помощи оператора равенства ( x == y ), он выдаст True. Но если мы при помощи того же оператора сравним идентификаторы объектов x и y (полученные с использованием функции id() ) – мы получим False. Дело в том, что во втором случае мы сравнивали адреса памяти переменных, а они разные – расположены в разных местах. Хотя значения, которые содержат эти переменные, одинаковы.

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

Неизменяемые типы данных

Давайте рассмотрим некоторые неизменяемые типы.

Целые числа (int)

Давайте определим переменную x , имеющую значение 10. Встроенный метод id() используется для определения местоположения x в памяти, а type() используется для определения типа переменной. Когда мы пытаемся изменить значение x , оно успешно изменяется.

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

Строки (str)

То же самое верно и для строкового типа данных. Мы не можем изменить существующую переменную, вместо этого мы должны создать новую с тем же именем.

Кортежи (tuple)

Давайте разберем кортежи. Мы определили кортеж с 4 значениями. Воспользуемся функцией id() для вывода его адреса. Если мы захотим изменить значение первого элемента, то получим ошибку TypeError. Это означает, что кортеж не поддерживает присвоение или обновление элементов.

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

Числа с плавающей запятой (float)

У нас есть переменная x типа float. Используя функцию id() , мы можем узнать ее адрес. Если мы попробуем заменить элемент с индексом 1, то получим TypeError. Как и в предыдущих примерах видим, что float не поддерживает модификацию элемента.

Если же мы обновим float, переопределив его, то при вызове получим новое значение и новый адрес.

Изменяемые типы данных

Теперь давайте рассмотрим некоторые изменяемые типы.

Списки (list)

Определим список с именем x и добавим в него некоторые значения. После этого обновим список: присвоим новое значение элементу с индексом 1. Можем заметить, что операция успешно выполнилась.

Вышеописанные действия являются простым и базовым примером модификации. Чтобы проверить изменчивость на более глубоком уровне, давайте рассмотрим тот же пример с небольшими изменениями.

Создадим новое имя y и свяжем его с тем же объектом списка. А теперь проверим, совпадает ли x с y . Нам вернется True. Кроме того, x и y имеют одинаковые адреса памяти.

Теперь добавим новое значение к списку x и проверим обновленный вывод.

Если мы теперь вызовем y , то получим тот же список, что и при вызове x . Хотя непосредственно в y мы ничего не добавляли. Это означает, что по сути мы обновляем один список объектов, у которого есть два разных имени: x и y . Оба они одинаковы и имеют один адрес в памяти даже после модификации.

Словари (dict)

Словари — часто используемый тип данных в Python. Давайте посмотрим на их изменчивость.

Определим словарь под именем dict с тремя ключами и их значениями. Когда мы распечатаем его, отобразится все его содержимое. Можно распечатать каждое значение словаря отдельно, а также использовать ключи вместо индексов. Подробнее о добавлении элементов в словарь вы можете узнать тут.

Давайте изменим какое-нибудь значение в нашем словаре. Например, обновим значение для ключа Name . Выведем обновленный словарь. Значение изменилось. При этом сами ключи словаря неизменяемы.

Списки и кортежи: наглядный пример изменяемых и неизменяемых объектов

Давайте по отдельности определим список и кортеж. Убедимся, что в кортеже есть значение типа список, а в списке есть значение типа кортеж.

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

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

Заключение

Мы разобрали различия между изменяемым и неизменяемым объектами в Python. Стоит понимать, что всё в Python называется объектами. И главное различие между ними – являются они изменяемыми или неизменяемыми.

Если две переменные указывают на один и тот же объект, оператор 'is' вернет true. Но в первой строке сказано, что оба имеют одинаковый идентификатор, но оператор 'is' дает значение false.

Попробуйте сохранить значения: t2a, t2b = t+t, t*2 . Тогда у них не будет одного и того же id (за исключением случаев, когда они являются одним и тем же объектом из-за оптимизации small-int или сворачивания констант компилятора, конечно . )

Во всяком случае, это задокументировано функцией id .

Объекты, идентификаторы которых вы сравниваете, не совпадают с объектами, которые вы сравниваете с is (и они тоже не совпадают друг с другом).

user2357112 supports Monica

@abarnert: это не «объяснил». «Гарантируется, что id будет уникальным и постоянным для этого объекта в течение его времени жизни. Два объекта с неперекрывающимся временем жизни могут иметь одинаковое значение id ()». ничего не говорит нам, если мы оба не понимаем, что за объект Python t+t , obj.meth или s[0:3] , а также знает время жизни каждого из этих объектов. Документация id() вообще ничего не говорит ни об одном из них.

Ответы 2

В первом примере ваши объекты не перекрываются во времени: один создается, затем уничтожается, затем создается другой с тем же идентификатором.

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

не создает ли запятая кортеж с обоими объектами внутри?

@CIsForCookies Нет, запятая создает кортеж с обоими целыми числами, возвращаемыми id внутри, но не с целыми числами, переданными в качестве аргументов в id - они уже вышли из области видимости и были уничтожены. Подробнее см. мой ответ.

Two objects with non-overlapping lifetimes may have the same id() value.

И два объекта имеют неперекрывающиеся времена жизни.

Тот факт, что они являются частью одного и того же выражения кортежа, этого не меняет, потому что в состав кортежа входят не t+t и t*2 , а id(t+t) и id(t*2) . Таким образом, эти два целочисленных значения, возвращаемые id , имеют перекрывающееся время жизни, но аргументы, переданные в id , нет.

Один из способов понять это - посмотреть, как CPython компилирует код:

Итак, вот что происходит. (Я собираюсь выбрать значение для t , скажем, 1000 , чтобы упростить понимание.)

  • id , 1000 и 1000 помещаются в стек.
  • BINARY_ADD создает значение 2000 в местоположении 42838592 .
  • id вызывается для этого значения и возвращает значение 42838592 в местоположении, скажем, 42838616 .
  • Поскольку значение 42838592 больше не находится в стеке и нигде не сохранялось, параметр id был единственной ссылкой на него, поэтому, когда значение id уменьшается в конце функции, оно немедленно удаляется.
  • 1000 , 2 и BINARY_MULTIPLY помещаются в стек.
  • 2000 создает новый объект 42838592 . Поскольку местоположение id только что было возвращено в пул объектов, новое значение повторно использует это местоположение.
  • 42838592 возвращает другой 42838640 , на этот раз в местоположении, скажем, int .

Таким образом, два значения 4283592 , 4283592 и 2000 , имеют перекрывающиеся времена жизни (и первое перекрывается со вторым 2000 ), два t не перекрываются.

И, наконец, обратите внимание, что если 4 - небольшое число,

… Потому что все значения t (кроме исключительных случаев) являются ссылками на один и тот же объект.

Между тем, если 1000+1000 is 1000*2 - это константа, а не переменная, int может быть или не быть истинным, в зависимости от вашей версии CPython, из-за того, как работает сворачивание констант в модуле компиляции.

Все это говорит о том, что попытка на самом деле воспользоваться преимуществом того, являются ли два одинаковых 2000 одним и тем же объектом или нет, в значительной степени всегда ужасная идея. Единственная причина, по которой вам стоит задуматься об этом вопросе, - это если вы пытаетесь больше узнать о внутреннем устройстве CPython.

И, конечно же, все это зависит от CPython. Большинство других интерпретаторов Python используют ту или иную форму сборщика мусора вместо реф-подсчета, поэтому первый id вряд ли будет уничтожен до того, как будет создан второй. Кроме того, не все из них используют пул объектов, такой как CPython. Не говоря уже о том, что им разрешено делать совершенно разные вещи для t+t , если они могут гарантировать уникальные значения для неперекрывающихся объектов.

PyPy обычно возвращает здесь одно и то же значение - но только потому, что он изначально сворачивает t*2 и t+t в один и тот же объект; попробуйте с and you'll get completely different и t * 3 id id`s.

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