Python

is против == в Python — Сравнение объектов

Spread the love

Оригинальная статья: Olivera Popović — ‘is’ vs ‘==’ in Python — Object Comparison

В Python есть два очень похожих оператора для проверки равенства двух объектов. Эти два оператора is и ==.

Их обычно путаются друг с другом, потому что с простыми типами данных, такими как int и string (с которыми многие люди начинают изучать Python), они, кажется, делают то же самое:

x = 5
s = "example"

print("x == 5: " + str(x == 5))
print("x is 5: " + str(x is 5))
print("s == 'example': " + str(s == "example"))
print("s is 'example': " + str(s is "example"))

Запуск этого кода приведет к:

x == 5: True
x is 5: True
s == 'example': True
s is 'example': True

Это показывает, что == и is возвращает одинаковое значение (True) в этих случаях. Однако, если вы попытались сделать это с более сложной структурой:

some_list = [1]

print("some_list == [1]: " + str(some_list == [1]))
print("some_list is [1]: " + str(some_list is [1]))

Это приведет к:

some_list == [1]: True
some_list is [1]: False

Здесь становится очевидным, что эти операторы не одинаковы.

Разница заключается в том, что is проверяет идентичность (объектов), а == проверяет равенство (значения).

Вот еще один пример, который может прояснить разницу между этими двумя операторами:

some_list1 = [1]
some_list2 = [1]
some_list3 = some_list1

print("some_list1 == some_list2: " + str(some_list1 == some_list2))
print("some_list1 is some_list2: " + str(some_list1 is some_list2))
print("some_list1 == some_list3: " + str(some_list1 == some_list3))
print("some_list1 is some_list3: " + str(some_list1 is some_list3))

Это приводит к:

some_list1 == some_list2: True
some_list1 is some_list2: False
some_list1 == some_list3: True
some_list1 is some_list3: True

Как мы видим, some_list1 по значению равен some_list2 (они оба равны [1]]), но они не идентичны, то есть они не являются одним и тем же объектом, даже если они имеют одинаковые значения.

Тем не менее, some_list1 идентичен some_list3, так как они ссылаются на один и тот же объект в памяти.

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

Хотя эта часть проблемы теперь может быть понятна (когда мы использовали переменные), может возникнуть другой вопрос:

Почему is и == ведут себя одинаково с простыми значениями типа int и string (например, 5 и «example»), но не ведут себя одинаково со списками (такими как [1])?

В Python есть два типа данных: изменяемые и неизменяемые.

  • Изменяемые типы данных — это типы данных, которые вы можете «менять» со временем
  • Неизменяемые типы данных остаются неизменными (имеют одно и то же место в памяти, что is и проверяет) после их создания.

Изменяемые типы данных: list, dictionary, set и пользовательские классы.

Неизменяемые типы данных: int, float, decimal, bool, string, tuple и range.

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

Таким образом, используя значение 5 для переменной, — это будет то же самое 5, которые вы используете в других местах в вашем коде в одной и той же переменной, и то же самое касается строковых литералов.

Если вы используете строку «example» один раз, каждый раз, когда вы используете «example», это будет точно такой же объект. см. это примечание для дальнейшего разъяснения.

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

s = "example"
print("Id of s: " + str(id(s)))
print("Id of the String 'example': " + str(id("example")) + " (note that it's the same as the variable s)")
print("s is 'example': " + str(s is "example"))

print("Change s to something else, then back to 'example'.")
s = "something else"
s = "example"
print("Id of s: " + str(id(s)))
print("s is 'example': " + str(s is "example"))
print()

list1 = [1]
list2 = list1
print("Id of list1: " + str(id(list1)))
print("Id of list2: " + str(id(list2)))
print("Id of [1]: " + str(id([1])) + " (note that it's not the same as list1!)")
print("list1 == list2: " + str(list1 == list2))
print("list1 is list2: " + str(list1 is list2))

print("Change list1 to something else, then back to the original ([1]) value.")
list1 = [2]
list1 = [1]
print("Id of list1: " + str(id(list1)))
print("list1 == list2: " + str(list1 == list2))
print("list1 is list2: " + str(list1 is list2))

Это выводит:

Id of s: 22531456
Id of the String 'example': 22531456 (note that it's the same as the variable s)
s is 'example': True
Change s to something else, then back to 'example'.
Id of s: 22531456
s is 'example': True

Id of list1: 22103504
Id of list2: 22103504
Id of [1]: 22104664 (note that it's not the same as list1!)
list1 == list2: True
list1 is list2: True
Change list1 to something else, then back to the original ([1]) value.
Id of list1: 22591368
list1 == list2: True
list1 is list2: False

Мы можем видеть, что в первой части примера s вернулся к точно такому же «example» объекту, которому он был назначен в начале, даже если мы изменим значение s за это время.

Однако list не возвращает тот же объект со значением [1], он создает новый объект, даже если он имеет то же значение, что и в первый раз [1].

Если вы запустите приведенный выше код, вы, вероятно, получите разные идентификаторы для объектов, но равенства будут одинаковыми.

Когда использовать is и == ?

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

Кроме того, обычно is быстрее, чем оператор ==, потому что он просто проверяет целочисленное равенство адреса памяти.

Важное примечание: единственная ситуация, когда is работает точно так, как можно было бы ожидать, это с singleton классами или объектами (как например с None). Даже с неизменяемыми объектами бывают ситуации, когда is не работает должным образом.

Например, для больших объектов string, генерируемых некоторой кодовой логикой или большими целыми числами int, is может (и будет) вести себя непредсказуемо. Если вы не пройдете interning (проверку) (т.е. убедитесь, что существует только одна копия string / int), Поведение равенства со всеми различными неизменяемые объектами, которые вы планируете использовать с is будут непредсказуемыми.

Суть в следующем: используйте == в 99% случаев.

Если два объекта идентичны, они также равны, и обратное не всегда верно.

Переопределение операторов == и !=

Операторы != и is not не ведут себя так же, как их «положительные» коллеги (==/is). А именно, != возвращает True, если объекты не имеют одно и то же значение, в то время как is not не возвращает True, если объекты не хранятся в одном и том же адресе памяти.

Еще одно различие между этими двумя операторами заключается в том, что вы можете переопределить поведение == / != для пользовательского класса, в то время как вы не можете переопределить поведение is.

Если вы реализуете собственный метод __eq()__ в своем классе, вы можете изменить поведение операторов == / !=:

class TestingEQ:
    def __init__(self, n):
        self.n = n

    # используя '==', чтобы проверить, оба ли числа
    # четные, или если оба числа нечетные
    def __eq__(self, other):
        if (self.n % 2 == 0 and other % 2 == 0):
            return True
        else:
            return False


print(5 == TestingEQ(1))
print(2 == TestingEQ(10))
print(1 != TestingEQ(2))

Это приводит к:

False
True
True

Заключение

Короче говоря, == / != проверяет равенство (по значению) и is / is not проверяет идентичность двух объектов, то есть проверяет адреса их памяти.

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

Была ли вам полезна эта статья?
[12 / 5]

Spread the love
Editorial Team

View Comments

  • С целыми числами и строками всё не так просто. В питоне для первых 256 целых чисел используются предварительно созданные значения. Если мы используем такое число, питон не создаёт новое значение, а возвращает адрес готовой структуры.

    In [1]: x = 256
    In [2]: x == 256
    Out[2]: True
    In [3]: x is 256
    Out[3]: True

    In [4]: y = 257
    In [5]: y == 257
    Out[5]: True
    In [6]: y is 257
    Out[6]: False

    С относительно длинными строками is тоже вернёт False
    In [14]: s = 'gdfhjfdsnjkfndsjkfnjksd fdsflksgljnfsajgfsjngjkfdngjdfnjgkdfjkgnkjgnfjksngjkdsfnfdgjk'
    In [15]: s == 'gdfhjfdsnjkfndsjkfnjksd fdsflksgljnfsajgfsjngjkfdngjdfnjgkdfjkgnkjgnfjksngjkdsfnfdgjk'
    Out[15]: True
    In [16]: s is 'gdfhjfdsnjkfndsjkfnjksd fdsflksgljnfsajgfsjngjkfdngjdfnjgkdfjkgnkjgnfjksngjkdsfnfdgjk'
    Out[16]: False

  • In [8]: x=4

    In [9]: x is not 3
    Out[9]: True

    Т.е. is not тоже возвращает True?

  • id([0]) == id([0])
    out: True

    a = id([0])
    b = id([0])
    a == b
    out: False, если работаешь в терминале и True, если пускаешь скрипт.

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

12 месяцев ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

12 месяцев ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

2 года ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

2 года ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

2 года ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

2 года ago