Dlaczego porównywanie ciągów za pomocą „==” lub „is” czasami daje inny wynik?

1146

Mam program w języku Python, w którym dwie zmienne są ustawione na wartość 'public'. W wyrażeniu warunkowym mam porównanie, var1 is var2które się nie udaje, ale jeśli je zmienię var1 == var2, zwraca True.

Teraz, jeśli otworzę mój interpreter Pythona i zrobię to samo porównanie „jest”, to się powiedzie.

>>> s1 = 'public'
>>> s2 = 'public'
>>> s2 is s1
True

Czego tu brakuje?

jottos
źródło
8
patrz: stackoverflow.com/questions/1392433/…
Nick Dandoulakis
3
Ten problem występuje również, gdy odczytujesz dane wejściowe konsoli poprzez np input = raw_input("Decide (y/n): "). : W takim przypadku wprowadzenie „y” i if input == 'y':zwróci „True”, a if input is 'y':zwróci False.
Semjon Mössinger
4
Ten blog zawiera znacznie pełniejsze wyjaśnienie niż jakakolwiek odpowiedź guilload.com/python-string-interning
Chris_Rands
1
Jak wspomina @ Chris-rico, mam świetne wyjaśnienie tutaj stackoverflow.com/q/15541404/1695680
ThorSummoner

Odpowiedzi:

1533

is jest testowanie tożsamości, == to testowanie równości. to, co dzieje się w twoim kodzie, byłoby emulowane w interpretera w następujący sposób:

>>> a = 'pub'
>>> b = ''.join(['p', 'u', 'b'])
>>> a == b
True
>>> a is b
False

nic więc dziwnego, że nie są takie same, prawda?

Innymi słowy: isjestid(a) == id(b)

SilentGhost
źródło
17
ahh to samo co eq? czy równy? w planie, rozumiem.
jottos
47
Lub ==vs .equals()w Javie. Najlepsze jest to, że Python ==nie jest analogiczny do Javy ==.
MatrixFrog,
11
@ Крайст: istnieje tylko jedna Nonewartość. Więc zawsze ma ten sam identyfikator.
SilentGhost,
18
Nie dotyczy to przykładu „jest -> Prawda” PO.
user2864740,
6
@AlexanderSupertramp, z powodu internowania łańcucha .
Chris Rico,
569

Inne odpowiedzi tutaj są poprawne: issłuży do porównania tożsamości , natomiast ==służy do porównania równości . Ponieważ zależy Ci na równości (dwa ciągi powinny zawierać te same znaki), w tym przypadku isoperator jest po prostu zły i powinieneś używać ==zamiast tego.

Przyczyną istego jest interakcja polegająca na tym, że (większość) literałów łańcuchowych jest domyślnie internowana . Z Wikipedii:

Wewnętrzne ciągi przyśpieszają porównania ciągów, które czasami są wąskim gardłem w aplikacjach (takich jak kompilatory i środowiska wykonawcze dynamicznego języka programowania), które w dużym stopniu opierają się na tabelach skrótów z kluczami ciągów. Bez internowania sprawdzenie, czy dwa różne ciągi znaków są równe, obejmuje sprawdzenie każdego znaku obu ciągów. Jest to powolne z kilku powodów: z natury jest równe O (n) długości łańcuchów; zazwyczaj wymaga odczytów z kilku regionów pamięci, które wymagają czasu; a odczyty zapełniają pamięć podręczną procesora, co oznacza, że ​​dostępna jest mniejsza pamięć podręczna na inne potrzeby. W przypadku łańcuchów internowanych wystarczy prosty test tożsamości obiektu po oryginalnej operacji internowania; jest to zwykle realizowane jako test równości wskaźnika,

Tak więc, jeśli masz w programie dwa literały łańcuchowe (słowa, które są dosłownie wpisane do kodu źródłowego programu, otoczone znakami cudzysłowu), które mają tę samą wartość, kompilator Python automatycznie internalizuje ciągi znaków, dzięki czemu oba są przechowywane w tym samym miejscu lokalizacja pamięci. (Pamiętaj, że nie zawsze tak się dzieje, a zasady, kiedy to się dzieje, są dość skomplikowane, więc proszę nie polegać na tym zachowaniu w kodzie produkcyjnym!)

Ponieważ w sesji interaktywnej oba ciągi są faktycznie przechowywane w tej samej lokalizacji pamięci, mają tę samą tożsamość , więc isoperator działa zgodnie z oczekiwaniami. Ale jeśli skonstruujesz ciąg za pomocą innej metody (nawet jeśli ten ciąg zawiera dokładnie takie same znaki), wówczas ciąg może być równy , ale nie jest to ten sam ciąg - to znaczy ma inną tożsamość , ponieważ jest przechowywane w innym miejscu w pamięci.

Daniel Pryden
źródło
6
Gdzie można przeczytać więcej o zawiłych regułach dotyczących internowania łańcuchów?
Noctis Skytower
88
+1 za dokładne wyjaśnienie. Nie jestem pewien, jak druga odpowiedź otrzymała tyle głosów poparcia, bez wyjaśnienia, co faktycznie się stało.
That1Guy,
4
dokładnie o tym myślałem, kiedy czytałem pytanie. Przyjęta odpowiedź jest krótka, ale zawiera fakt, ale ta odpowiedź wyjaśnia sprawy znacznie lepiej. Miły!
Sнаđошƒаӽ
3
@NoctisSkytower Googled to samo i znalazłem to guilload.com/python-string-interning
xtreak
5
@ naught101: Nie, regułą jest wybór pomiędzy ==i isna podstawie tego, jaki rodzaj czeku chcesz. Jeśli zależy ci na równości łańcuchów (czyli o tej samej zawartości), zawsze powinieneś używać ==. Jeśli zależy ci na tym, czy jakieś dwie nazwy Python odnoszą się do tej samej instancji obiektu, powinieneś użyć is. Możesz potrzebować, isjeśli piszesz kod, który obsługuje wiele różnych wartości, nie troszcząc się o ich zawartość, lub jeśli wiesz, że jest tylko jedna z nich i chcesz zignorować inne obiekty, które podają się za takie rzeczy. Jeśli nie jesteś pewien, zawsze wybieraj ==.
Daniel Pryden,
108

Słowo iskluczowe jest testem tożsamości obiektu podczas== stanowi porównanie wartości.

Jeśli użyjesz is, wynik będzie prawdziwy tylko wtedy, gdy obiekt jest tym samym obiektem. Jednak ==będzie prawdziwe każdym razem wartości obiektu są takie same.

Thomas Owens
źródło
57

Na koniec należy zwrócić uwagę na tę sys.internfunkcję, aby upewnić się, że otrzymujesz odwołanie do tego samego ciągu:

>>> from sys import intern
>>> a = intern('a')
>>> a2 = intern('a')
>>> a is a2
True

Jak wskazano powyżej, nie należy używać isdo określania równości ciągów. Ale może to być pomocne, jeśli masz jakieś dziwne wymagania dotyczące używaniais .

Zauważ, że internfunkcja była wbudowana w Python 2, ale została przeniesiona do sysmodułu w Python 3.

Jason Baker
źródło
43

isto testowanie tożsamości, ==to testowanie równości. Oznacza to, że isjest to sposób na sprawdzenie, czy dwie rzeczy są takie same lub po prostu równoważne.

Powiedz, że masz prosty personprzedmiot. Jeśli nazywa się „Jack” i ma „23” lat, jest to odpowiednik innego 23-letniego Jacka, ale nie jest to ta sama osoba.

class Person(object):
   def __init__(self, name, age):
       self.name = name
       self.age = age

   def __eq__(self, other):
       return self.name == other.name and self.age == other.age

jack1 = Person('Jack', 23)
jack2 = Person('Jack', 23)

jack1 == jack2 #True
jack1 is jack2 #False

Są w tym samym wieku, ale nie są tym samym przykładem osoby. Ciąg może być równoważny z innym, ale nie jest to ten sam obiekt.

TankorSmash
źródło
Jeśli zmienisz zestaw jack1.age = 99, to się nie zmieni jack2.age. To dlatego, że są to dwa różne wystąpienia, więc jack1 is not jack2. Mogą jednak być sobie równi, jack1 == jack2jeśli ich nazwisko i wiek są takie same. To staje się bardziej skomplikowane dla ciągów, ponieważ ciągi są niezmienne w Pythonie, a Python często używa tej samej instancji. Podoba mi się to wyjaśnienie, ponieważ używa prostych przypadków (zwykły obiekt), a nie specjalnych przypadków (ciągów).
Flimm
37

To jest notatka poboczna, ale w pythonie idiomatycznym często zobaczysz takie rzeczy jak:

if x is None: 
    # some clauses

Jest to bezpieczne, ponieważ gwarantowana jest jedna instancja obiektu zerowego (tj. Brak) .

Gregg Lind
źródło
1
Czy to samo dotyczy prawdy i fałszu? Tylko jedna instancja będzie pasować?
HandyManDan
1
@HandyManDan Tak, są singletonami zarówno w pythonie 2, jak i 3.
kamillitw
@kamillitw, ale w Python 2 możesz ponownie przypisać False i True.
Martijn Pieters
28

Jeśli nie masz pewności, co robisz, użyj „==”. Jeśli masz trochę więcej wiedzy na ten temat, możesz użyć „jest” dla znanych obiektów, takich jak „Brak”.

W przeciwnym razie będziesz się zastanawiać, dlaczego coś nie działa i dlaczego tak się dzieje:

>>> a = 1
>>> b = 1
>>> b is a
True
>>> a = 6000
>>> b = 6000
>>> b is a
False

Nie jestem nawet pewien, czy pewne rzeczy pozostaną takie same między różnymi wersjami / implementacjami Pythona.

Mattias Nilsson
źródło
1
Ciekawy przykład pokazujący, jak ponowne przypisanie ints powoduje, że ten warunek jest wyzwalany. Dlaczego to się nie udało? Czy to z powodu internowania czy coś innego?
Paweł,
Wygląda na to, że powód, dla którego is zwraca false, może wynikać z implementacji interpretera: stackoverflow.com/questions/132988/…
Paul
@ArchitJain Tak, te linki wyjaśniają to całkiem dobrze. Po ich przeczytaniu dowiesz się, na jakich liczbach możesz użyć „jest”. Chciałbym tylko, żeby wyjaśnili, dlaczego nadal nie jest to dobry pomysł :) Wiesz, że nie jest to dobry pomysł, aby zakładać, że wszyscy inni też (lub że zinternalizowany zakres liczb nigdy się nie zmieni)
Mattias Nilsson
20

Z mojego ograniczonego doświadczenia w Pythonie, issłuży do porównywania dwóch obiektów, aby sprawdzić, czy są one tym samym obiektem, w przeciwieństwie do dwóch różnych obiektów o tej samej wartości. ==służy do ustalenia, czy wartości są identyczne.

Oto dobry przykład:

>>> s1 = u'public'
>>> s2 = 'public'
>>> s1 is s2
False
>>> s1 == s2
True

s1jest ciągiem Unicode i s2jest ciągiem normalnym. Nie są tego samego typu, ale mają tę samą wartość.

Jack M.
źródło
17

Myślę, że ma to związek z faktem, że gdy porównanie „jest” daje w wyniku fałsz, używane są dwa różne obiekty. Jeśli ma wartość true, oznacza to, że wewnętrznie używa tego samego dokładnego obiektu i nie tworzy nowego, być może dlatego, że utworzyłeś je w ułamku około 2 sekund i ponieważ nie ma dużej przerwy między zoptymalizowanym a używa tego samego obiektu.

Dlatego powinieneś używać operatora równości ==, a nie isdo porównywania wartości obiektu łańcuchowego.

>>> s = 'one'
>>> s2 = 'two'
>>> s is s2
False
>>> s2 = s2.replace('two', 'one')
>>> s2
'one'
>>> s2 is s
False
>>> 

W tym przykładzie stworzyłem s2, który był innym obiektem łańcuchowym poprzednio równym „jeden”, ale nie jest to ten sam obiekt s, ponieważ, ponieważ interpreter nie używał tego samego obiektu, ponieważ początkowo nie przypisałem go do „jednego”, gdybym miał, uczyniłby z nich ten sam przedmiot.

meder omuraliev
źródło
3
Podawanie .replace()jako przykładu w tym kontekście prawdopodobnie nie jest najlepsze, ponieważ jego semantyka może być myląca. s2 = s2.replace()będzie zawsze utworzyć nowy obiekt string, przypisać do nowego obiektu ciąg s2, a następnie rozporządzania przedmiotem strun, które s2wykorzystywane do punktu celu. Więc nawet gdybyś to zrobił s = s.replace('one', 'one'), nadal dostaniesz nowy obiekt łańcucha.
Daniel Pryden,
13

Wierzę, że jest to znane jako ciągi „internowane”. Python to robi, podobnie jak Java, podobnie jak C i C ++ podczas kompilacji w trybach zoptymalizowanych.

Jeśli użyjesz dwóch identycznych łańcuchów, zamiast marnować pamięć, tworząc dwa obiekty łańcuchowe, wszystkie łańcuchy wewnętrzne o tej samej zawartości wskazują na tę samą pamięć.

Powoduje to, że operator „jest” w Pythonie zwraca wartość True, ponieważ dwa ciągi o tej samej zawartości wskazują ten sam obiekt ciągu. Stanie się tak również w Javie i C.

Jest to przydatne tylko w celu oszczędzania pamięci. Nie można na nim polegać w celu sprawdzenia równości łańcuchów, ponieważ różne interpretery i kompilatory oraz silniki JIT nie zawsze mogą to zrobić.

Zan Lynx
źródło
12

Odpowiadam na pytanie, mimo że pytanie jest stare, ponieważ żadna z powyższych odpowiedzi nie cytuje odniesienia do języka

W rzeczywistości operator sprawdza tożsamość, a operator == sprawdza równość,

Z języka odniesienia:

Typy wpływają na prawie wszystkie aspekty zachowania obiektu. W pewnym sensie ma to wpływ nawet na znaczenie tożsamości obiektu: w przypadku typów niezmiennych operacje obliczające nowe wartości mogą faktycznie zwracać odwołanie do dowolnego istniejącego obiektu o tym samym typie i wartości, podczas gdy w przypadku obiektów zmiennych jest to niedozwolone . Np. Po a = 1; b = 1, aib mogą, ale nie muszą odnosić się do tego samego obiektu o wartości jeden, w zależności od implementacji, ale po c = []; d = [], c id gwarantują, że odnoszą się do dwóch różnych, unikalnych, nowo utworzonych pustych list. (Zauważ, że c = d = [] przypisuje ten sam obiekt do c i d.)

dlatego z powyższego stwierdzenia możemy wywnioskować, że ciągi, które są niezmiennym typem, mogą zawieść, gdy są sprawdzane za pomocą „is” i mogą sprawdzać się, gdy sprawdzane za pomocą „is”

To samo dotyczy int, tuple, które są również typami niezmiennymi

Baran
źródło
8

==Operatora Wartość testu równoważności. isTożsamość testy operator obiektu, testy Python czy te dwa są naprawdę ten sam obiekt (czyli na żywo pod tym samym adresem w pamięci).

>>> a = 'banana'
>>> b = 'banana'
>>> a is b 
True

W tym przykładzie Python utworzył tylko jeden obiekt łańcuchowy i oba, ai bodnosi się do niego. Powodem jest to, że Python wewnętrznie buforuje i ponownie wykorzystuje niektóre ciągi jako optymalizację, tak naprawdę w pamięci jest tylko ciąg „banan”, współdzielony przez a i b; Aby wyzwolić normalne zachowanie, musisz użyć dłuższych ciągów:

>>> a = 'a longer banana'
>>> b = 'a longer banana'
>>> a == b, a is b
(True, False)

Kiedy tworzysz dwie listy, otrzymujesz dwa obiekty:

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False

W tym przypadku powiedzielibyśmy, że dwie listy są równoważne, ponieważ mają te same elementy, ale nie identyczne, ponieważ nie są tym samym obiektem. Jeśli dwa obiekty są identyczne, są również równoważne, ale jeśli są równoważne, niekoniecznie są identyczne.

Jeśli aodnosi się do obiektu, a ty przypisujesz b = a, wówczas obie zmienne odnoszą się do tego samego obiektu:

>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True
X. Wang
źródło
7

isporówna lokalizację pamięci. Służy do porównywania na poziomie obiektu.

==porówna zmienne w programie. Służy do sprawdzania na poziomie wartości.

is sprawdza równoważność poziomu adresu

== sprawdza równoważność poziomu wartości

johnashu
źródło
3

isto testowanie tożsamości, ==to testowanie równości (patrz Dokumentacja Pythona ).

W większości przypadków, jeśli a is b, to a == b. Ale są wyjątki, na przykład:

>>> nan = float('nan')
>>> nan is nan
True
>>> nan == nan
False

Tak więc możesz używać tylko isdo testów tożsamości, nigdy testów równości.

Ryan
źródło