Python! = Операция против «нет»

250

В комментарии к этому вопросу я увидел утверждение, в котором рекомендуется использовать

result is not None

против

result != None

Мне было интересно, в чем разница, и почему один может быть рекомендован по сравнению с другим?

viksit
источник
3
обман: stackoverflow.com/questions/1504717/…
SilentGhost
1
Хм. Хотя ответ на оба вопроса - одна и та же концепция, я думаю, что положительные отзывы и подробные ответы здесь вносят независимый вклад в концепцию тестирования на идентичность и равенство.
viksit

Ответы:

301

==это тест на равенство . Он проверяет, являются ли правая сторона и левая сторона равными объектами (согласно их __eq__или __cmp__методам.)

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

Вы используете isis not) для одиночных игр, например None, когда вас не волнуют объекты, которые вы хотите притвориться, Noneили где вы хотите защитить от разрушения объектов при сравнении с ними None.

Томас Воутерс
источник
3
Спасибо за ответ - не могли бы вы рассказать о ситуациях, когда объект может сломаться по сравнению с None?
viksit
3
@viksit. Noneимеет мало методов и почти не имеет атрибутов. Если в вашем __eq__тесте ожидался метод или атрибут, он может сломаться. def __eq__( self, other ): return self.size == other.size, Например, сломается, если otherслучится None.
S.Lott
36
Мой любимый способ понять это: Python isпохож на Java ==. Питон ==похож на Java .equals(). Конечно, это помогает, только если вы знаете Java.
MatrixFrog
4
@MatrixFrog: В PHP или JavaScript мы бы сказали, что isэто похоже ===(очень равно), и наоборот is not, похоже !==(не совсем равно).
Орвеллофил
3
Это is notединственный оператор или это просто отрицание результата isкак внутри not foo is bar?
Асад Моосви
150

Во-первых, позвольте мне остановиться на нескольких терминах. Если вы просто хотите, чтобы на ваш вопрос ответили, прокрутите вниз до пункта «Ответ на ваш вопрос».

Определения

Идентификация объекта : когда вы создаете объект, вы можете назначить его переменной. Затем вы можете также назначить его другой переменной. И другой.

>>> button = Button()
>>> cancel = button
>>> close = button
>>> dismiss = button
>>> print(cancel is close)
True

В этом случае cancel, closeи dismissвсе они относятся к одному объекту в памяти. Вы создали только один Buttonобъект, и все три переменные ссылаются на этот один объект. Мы говорим, что cancel, closeи dismissвсе ссылаются на идентичные объекты; то есть они относятся к одному объекту.

Равенство объекта : Если сравнить два объекта, то , как правило , не волнует , что он относится к точному и тому же объекту в памяти. С помощью равенства объектов вы можете определить свои собственные правила сравнения двух объектов. Когда вы пишете if a == b:, вы, по сути, говорите if a.__eq__(b):. Это позволяет вам определить __eq__метод, aчтобы вы могли использовать свою собственную логику сравнения.

Обоснование сравнений на равенство

Обоснование: два объекта имеют одинаковые данные, но не идентичны. (Это не один и тот же объект в памяти.) Пример: строки

>>> greeting = "It's a beautiful day in the neighbourhood."
>>> a = unicode(greeting)
>>> b = unicode(greeting)
>>> a is b
False
>>> a == b
True

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

Здесь у меня есть две строки Unicode, aи b. У них одинаковое содержание, но они не являются одним и тем же объектом в памяти. Однако, когда мы сравниваем их, мы хотим, чтобы они сравнивались на равных. Здесь происходит то, что объект Unicode реализовал __eq__метод.

class unicode(object):
    # ...

    def __eq__(self, other):
        if len(self) != len(other):
            return False

        for i, j in zip(self, other):
            if i != j:
                return False

        return True

Примечание: __eq__он unicodeопределенно реализован более эффективно, чем этот.

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

>>> import datetime
>>> a = Monitor()
>>> a.make = "Dell"
>>> a.model = "E770s"
>>> a.owner = "Bob Jones"
>>> a.warranty_expiration = datetime.date(2030, 12, 31)
>>> b = Monitor()
>>> b.make = "Dell"
>>> b.model = "E770s"
>>> b.owner = "Sam Johnson"
>>> b.warranty_expiration = datetime.date(2005, 8, 22)
>>> a is b
False
>>> a == b
True

Здесь у меня есть два монитора Dell, aи b. Они имеют одинаковую марку и модель. Однако они не имеют одинаковых данных и не являются одним и тем же объектом в памяти. Однако, когда мы сравниваем их, мы хотим, чтобы они сравнивались на равных. Здесь происходит то, что объект Monitor реализует __eq__метод.

class Monitor(object):
    # ...

    def __eq__(self, other):
        return self.make == other.make and self.model == other.model

Отвечая на ваш вопрос

При сравнении Noneвсегда используйте is not. Ни один из них не является синглтоном в Python - в памяти есть только один его экземпляр.

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

Сравнивая равенство , Python должен выяснить, есть ли у вашего объекта __eq__метод. Если это не так, он проверяет каждый суперкласс в поисках __eq__метода. Если он находит, Python вызывает его. Это особенно плохо, если __eq__метод медленный и не сразу возвращается, когда замечает, что другой объект None.

Вы не реализовали __eq__? Тогда Python, вероятно, найдет __eq__метод objectи использует его вместо этого - который все равно проверяет идентичность объекта.

При сравнении большинства других вещей в Python вы будете использовать !=.

Wesley
источник
42

Учтите следующее:

class Bad(object):
    def __eq__(self, other):
        return True

c = Bad()
c is None # False, equivalent to id(c) == id(None)
c == None # True, equivalent to c.__eq__(None)
Алок Сингхал
источник
1
Это очень полезный и простой пример. Спасибо.
мсарафзаде
18

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

Игнасио Васкес-Абрамс
источник
Ах, интересно! В каких ситуациях можно подделать сравнение равенства между прочим? Я предполагаю, что это имеет какое-то значение для безопасности.
viksit
1
Речь идет не о фальсификации равенства, а о реализации равенства. Есть много причин, чтобы хотеть определить, как объект сравнивается с другим.
Томас Воутерс
1
Я бы сказал, что это больше путаницы, чем безопасности.
Грег Хьюгилл
2
Я не выдвинул причину подделки равенства против None, но неправильное поведение в отношении Noneмогло бы возникнуть как побочный эффект от реализации равенства против других типов. Это не столько последствия для безопасности, сколько просто последствия для правильности.
Игнасио Васкес-Абрамс
Ах, таким образом, я вижу. Спасибо за разъяснение.
viksit
10
>>> () есть ()
Правда
>>> 1 есть 1
Правда
>>> (1,) == (1,)
Правда
>>> (1,) есть (1,)
Ложь
>>> a = (1,)
>>> б = а
>>> а б
Правда

Некоторые объекты являются синглетонами и, следовательно, isс ними эквивалентны ==. Большинство нет.

ephemient
источник
4
Большинство из них работают только по совпадению / деталям реализации. ()и 1не являются по своей сути одиночками.
Майк Грэм
1
В реализации CPython маленькие целые числа ( -NSMALLNEGINTS <= n <= NSMALLPOSINTS) и пустые кортежи являются синглетонами. На самом деле это не задокументировано и не гарантировано, но вряд ли изменится.
2010 г.
3
Это то, как это реализовано, но это не имеет смысла, не полезно и не полезно для обучения.
Майк Грэм
1
И, в частности, CPython - не единственная реализация Python. Полагаться на поведение, которое может варьироваться в разных реализациях Python, как правило, кажется мне плохой идеей.
me_and