
Новички часто путаются в конструкциях is
и ==
. Давайте разберемся, что к чему.
Сразу к сути: ==
(и его антагонист !=
) применяются для проверки равенства (неравенства) значения двух объектов. Значение, это непосредственно то, что лежит в переменной. Значение числа 323235 – собственно число 323235. Тавтология. Но на примерах станет яснее.
Оператор is
(и его антагонист is not
) применяются проверки равенства (неравенства) ссылок на объект. Сразу отметим то, что на значение (допустим 323235) может быть копировано и храниться в разных местах (в разных объектах в памяти).
>> x = 323235 >> y = 323235 >> x == y True >> x is y False
Видите, значение переменных равны по значению, но они ссылаются на разные объекты. Я не случайно взял большое число 323235. Дело в том, что в целях оптимизации интерпретатор Python при старте создает некоторые количество часто-используемых констант (от -5 до 256 включительно).
Следите внимательно за ловкостью рук:
>>> x = 256 >>> y = 256 >>> x is y True >>> x = 257 >>> y = 257 >>> x is y False >>> x = -5 >>> y = -5 >>> x is y True >>> x = -6 >>> y = -6 >>> x is y False
Поэтому новички часто совершают ошибку, считая, что писать ==
– это как-то не Python-way, а is
– Python-way. Это ошибочное предположение может быть раскрыто не сразу.
Python старается кэшировать и переиспользовать строковые значения. Поэтому весьма вероятно, что переменные, содержащие одинаковые строки, будут содержать ссылки на одинаковые объекты. Но это не факт! Смотрите последний пример:
>>> x = "hello" >>> y = "hello" >>> x is y True >>> x = "hel" + "lo" >>> y = "hello" >>> x is y True >>> a = "hel" >>> b = "lo" >>> x = a + b >>> y = "hello" >>> x == y True >>> x is y False
Мы составили строку из двух частей и она попала в другой объект. Python не догадался (и правильно) поискать ее в существующих строках.
Суть is (id)
В Python есть встроенная функция id
. Она возвращает идентификатор объекта – некоторое число. Гарантируется, что оно будет различно для различных объектах в пределах одного интерпретатора. В реализации CPython – это просто адрес объекта в памяти интерпретатора.
Так вот:
a is b
Это тоже самое, что:
id(a) == id(b)
И все! Пример для проверки:
>>> x = 10.40 >>> y = 10.40 >>> x is y False >>> x == y True >>> id(x) 4453475504 >>> id(y) 4453475600 >>> id(x) == id(y) False >>> x = y >>> x is y True >>> id(x) 4453475600 >>> id(y) 4453475600
Значения переменных равны, но их id
– разные, и is
выдает False
. Как только мы к x
привязали y
, то ссылки стали совпадать.
Для чего можно применять is?
Если мы точно знаем уверены, что хотим проверять именно равенство ссылок на объекты (один ли это объект в памяти или разные).
Еще можно применять is
для сравнения с None
. None
– это встроенная константа и двух None
быть не может.
>>> x is None False >>> x = None >>> x is None True
Также для Ellipsis:
>>> ... is Ellipsis True >>> x = ... >>> y = ... >>> x is y True
Я не рекомендую применять is
для True
и False
.
Потому что короче писать if x:
, чем if x is True:
.
Можно применять is
для сравнения типов с осторожностью (без учета наследования, т. е. проверка на точное совпадение типов):
>>> x = 10.5 >>> type(x) is float True
С наследованием может быть конфуз:
>>> class Foo: ... ... >>> class Bar(Foo): ... ... >>> f = Foo() >>> b = Bar() >>> type(f) is Foo True >>> type(b) is Bar True >>> type(b) is Foo False >>> isinstance(b, Foo) True
Не смотря на то, что Bar
– наследник Foo
, типы переменных foo
и bar
не совпадают. Если нам важно учесть наcледование, то пишите isinstance
.
Нюанс: is not против is (not)
Важно знать, что is not
– это один целый оператор, аналогичный id(x) != id(y)
. А в конструкции x is (not y)
– у нас сначала будет логическое отрицание y
, а потом просто оператор is
.
Пример уловки:
>>> x = 10 >>> x is not None True >>> x is (not None) False
Сравнение пользовательских классов
Далее речь пойдет об обычных ==
и !=
. Можно определить магический метод __eq__
, который обеспечит поведение при сравнении классов. Если он не реализован, то объекты будет сравниваться по ссылкам (как при is
).
>>> class Baz: ... ... >>> x = Baz() >>> y = Baz() >>> x == y False >>> x = y >>> x == y True
Если он реализован, то будет вызван метод __eq__
для левого операнда.
class Foo: def __init__(self, x): self.x = x def __eq__(self, other): print('Foo __eq__ {} and {}'.format(self, other)) return self.x == other.x >>> x = Foo(5) >>> y = Foo(5) >>> x == y Foo __eq__ <__main__.Foo object at 0x109e9c048> and <__main__.Foo object at 0x109e8a5c0> True
Метод __ne__
отвечает за реализацию !=
. По умолчанию он вызывает not x.__eq__(y)
. Но рекомендуется реализовывать их оба вручную, чтобы поведение сравнения было согласовано и явно.
Вопрос к размышлению: что будет если мы сравним объекты разных классов, причем оба класса реализуют __eq__
?
Что будет, если мы реализуем __ne__
, но не реализуем __eq__
?
А еще есть метод __cmp__
. Это уже выходит за рамки статьи про is
. Почитайте самостоятельно…
Специально для канала @pyway.