Python! = Operacja vs „nie jest”

250

W komentarzu do tego pytania widziałem stwierdzenie, które zaleca użycie

result is not None

vs

result != None

Zastanawiałem się, na czym polega różnica i dlaczego jedno może być polecane bardziej niż drugie?

viksit
źródło
1
Hmm Chociaż odpowiedź na oba pytania jest tym samym pojęciem, myślę, że pozytywne opinie i szczegółowe odpowiedzi tutaj przyczyniają się niezależnie do koncepcji testowania tożsamości i równości.
viksit

Odpowiedzi:

301

==jest testem równości . Sprawdza, czy z prawej strony i lewa strona są równe obiektów (zgodnie z ich __eq__lub __cmp__metod).

isjest testem tożsamości . Sprawdza, czy prawa i lewa strona są tym samym przedmiotem. Nie są wykonywane żadne wywołania metod, obiekty nie mogą wpływać na isoperację.

Używasz is(i is not) do singletonów, np. NoneTam, gdzie nie przejmujesz się przedmiotami, które mogłyby udawać, że Nonejesteś lub gdzie chcesz chronić przed pękaniem przedmiotów podczas porównywania None.

Thomas Wouters
źródło
3
Dzięki za odpowiedź - czy mógłbyś omówić sytuacje, w których przedmiot może się złamać, w porównaniu do Brak?
viksit
3
@viksit. Nonema kilka metod i prawie żadnych atrybutów. Jeśli __eq__test oczekiwał metody lub atrybutu, może się on nie powieść. def __eq__( self, other ): return self.size == other.size. Na przykład pęknie, jeśli othertak się stanie None.
S.Lott,
36
Mój ulubiony sposób to zrozumieć: Python isjest jak Java ==. Python's ==jest jak Java .equals(). Oczywiście pomaga to tylko wtedy, gdy znasz Javę.
MatrixFrog,
4
@MatrixFrog: W PHP lub JavaScript powiedzielibyśmy, że isjest podobny ===(bardzo równy) i odwrotnie is notjest podobny !==(nie do końca równy).
Orwellophile,
3
Czy jest is notto pojedynczy operator, czy po prostu neguje wynik ispodobny do wewnętrznego not foo is bar?
Asad Moosvi,
150

Najpierw pozwól mi przejść przez kilka terminów. Jeśli chcesz tylko uzyskać odpowiedź na pytanie, przewiń w dół do „Odpowiedzi na pytanie”.

Definicje

Tożsamość obiektu : Kiedy tworzysz obiekt, możesz przypisać go do zmiennej. Następnie możesz przypisać ją do innej zmiennej. I kolejny.

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

W tym przypadku cancel, closei dismisswszystkie odnoszą się do tego samego obiektu w pamięci. Utworzyłeś tylko jeden Buttonobiekt, a wszystkie trzy zmienne odnoszą się do tego jednego obiektu. Mówimy, że cancel, closei dismisswszystkie odnoszą się do identycznych obiektów; to znaczy odnoszą się do jednego obiektu.

Równość obiektów : porównując dwa obiekty, zwykle nie przejmujesz się, że odnosi się to dokładnie do tego samego obiektu w pamięci. Dzięki równości obiektów możesz zdefiniować własne reguły porównywania dwóch obiektów. Kiedy piszesz if a == b:, zasadniczo mówisz if a.__eq__(b):. Umożliwia to zdefiniowanie __eq__metody, adzięki czemu można użyć własnej logiki porównywania.

Uzasadnienie porównań równości

Uzasadnienie: dwa obiekty mają dokładnie takie same dane, ale nie są identyczne. (Nie są tym samym obiektem w pamięci.) Przykład: Ciągi znaków

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

Uwaga: Używam tutaj ciągów znaków Unicode, ponieważ Python jest wystarczająco inteligentny, aby ponownie używać zwykłych ciągów bez tworzenia nowych w pamięci.

Tutaj mam dwa ciągi Unicode ai b. Mają dokładnie taką samą treść, ale nie są tym samym obiektem w pamięci. Jednak porównując je, chcemy, aby były one równe. To, co się tutaj dzieje, polega na tym, że obiekt Unicode zaimplementował tę __eq__metodę.

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

Uwaga: __eq__on unicodejest zdecydowanie zaimplementowany bardziej wydajnie niż to.

Uzasadnienie: Dwa obiekty mają różne dane, ale są uważane za ten sam obiekt, jeśli niektóre kluczowe dane są takie same. Przykład: większość typów danych modelu

>>> 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

Tutaj mam dwa monitory Dell ai b. Mają tę samą markę i model. Nie mają one jednak tych samych danych ani nie są tym samym obiektem w pamięci. Jednak porównując je, chcemy, aby były one równe. W tym przypadku obiekt Monitor zaimplementował tę __eq__metodę.

class Monitor(object):
    # ...

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

Odpowiadając na twoje pytanie

Porównując do None, zawsze używaj is not. W Pythonie żaden nie jest singletonem - w pamięci jest tylko jedno jego wystąpienie.

Porównując tożsamość , można to zrobić bardzo szybko. Python sprawdza, czy obiekt, do którego się odwołujesz, ma taki sam adres pamięci jak globalny obiekt None - bardzo, bardzo szybkie porównanie dwóch liczb.

Porównując równość , Python musi sprawdzić, czy twój obiekt ma __eq__metodę. Jeśli nie, bada każdą nadklasę szukającą __eq__metody. Jeśli je znajdzie, Python go wywołuje. Jest to szczególnie złe, jeśli __eq__metoda jest powolna i nie wraca natychmiast, gdy zauważy, że jest inny obiekt None.

Nie wdrożyłeś __eq__? Wówczas Python prawdopodobnie znajdzie __eq__metodę objecti zastosuje ją zamiast tego - i tak po prostu sprawdza tożsamość obiektu.

Porównując większość innych rzeczy w Pythonie, będziesz używać !=.

Wesley
źródło
42

Rozważ następujące:

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)
Alok Singhal
źródło
1
To bardzo pomocny i prosty przykład. Dziękuję Ci.
msarafzadeh
18

Nonejest singletonem, dlatego porównanie tożsamości zawsze będzie działać, podczas gdy obiekt może sfałszować porównanie równości za pomocą .__eq__().

Ignacio Vazquez-Abrams
źródło
Ach ciekawe! W jakich sytuacjach można chcieć sfałszować porównanie równości między sobą? Domyślam się, że ma to w jakiś sposób wpływ na bezpieczeństwo.
viksit
1
Nie chodzi o fałszowanie równości, chodzi o wdrażanie równości. Istnieje wiele powodów, dla których warto zdefiniować porównanie obiektu z innym.
Thomas Wouters
1
Powiedziałbym, że to bardziej implikacje zamieszania niż implikacje dla bezpieczeństwa.
Greg Hewgill
2
Nie spotkałem się z powodem fałszywej równości None, ale niewłaściwe zachowanie Nonemoże wystąpić jako efekt uboczny wprowadzenia równości wobec innych typów. To nie tyle implikacje bezpieczeństwa, co implikacje poprawności.
Ignacio Vazquez-Abrams
Ach, w ten sposób, rozumiem. Dziękuję za wyjaśnienie.
viksit
10
>>> () jest ()
Prawdziwe
>>> 1 to 1
Prawdziwe
>>> (1,) == (1,)
Prawdziwe
>>> (1,) to (1,)
Fałszywe
>>> a = (1,)
>>> b = a
>>> a jest b
Prawdziwe

Niektóre obiekty są singletonami, a zatem isz nimi jest równoważne ==. Większość nie.

efemeryczny
źródło
4
Większość z nich działa tylko według szczegółów koincydencji / implementacji. ()i 1nie są z natury singletonami.
Mike Graham
1
W implementacji CPython małe liczby całkowite ( -NSMALLNEGINTS <= n <= NSMALLPOSINTS) i puste krotki singletonami. Rzeczywiście nie jest to udokumentowane ani gwarantowane, ale raczej nie ulegnie zmianie.
ephemient
3
Jest tak, jak jest wdrażany, ale nie ma znaczenia, nie jest użyteczny ani edukacyjny.
Mike Graham,
1
W szczególności CPython nie jest jedyną implementacją Pythona. Poleganie na zachowaniu, które może się różnić w zależności od implementacji Pythona, wydaje mi się ogólnie złym pomysłem.
me_i