Dlaczego poniższe zachowania zachowują się nieoczekiwanie w Pythonie?
>>> a = 256
>>> b = 256
>>> a is b
True # This is an expected result
>>> a = 257
>>> b = 257
>>> a is b
False # What happened here? Why is this False?
>>> 257 is 257
True # Yet the literal numbers compare properly
Używam Python 2.5.2. Próbując różnych wersji Pythona, wygląda na to, że Python 2.3.3 pokazuje powyższe zachowanie między 99 a 100.
Na podstawie powyższego mogę wysunąć hipotezę, że Python jest wewnętrznie zaimplementowany, tak że „małe” liczby całkowite są przechowywane w inny sposób niż większe liczby całkowite, a is
operator może odróżnić. Dlaczego nieszczelna abstrakcja? Jaki jest lepszy sposób porównywania dwóch dowolnych obiektów, aby sprawdzić, czy są one takie same, gdy nie wiem z góry, czy są to liczby, czy nie?
Odpowiedzi:
Popatrz na to:
Oto, co znalazłem w dokumentacji Python 2, „Plain Integer Objects” (to samo dotyczy Python 3 ):
źródło
Podsumowując - podkreślę: nie używaj
is
do porównywania liczb całkowitych.To nie jest zachowanie, którego powinieneś się spodziewać.
Zamiast tego użyj
==
i,!=
aby porównać odpowiednio dla równości i nierówności. Na przykład:Wyjaśnienie
Aby to wiedzieć, musisz wiedzieć, co następuje.
Po pierwsze, co robi
is
? Jest operatorem porównania. Z dokumentacji :A zatem poniższe są równoważne.
Z dokumentacji :
Zauważ, że fakt, że identyfikator obiektu w CPython (referencyjna implementacja Pythona) to lokalizacja w pamięci, jest szczegółem implementacji. Inne implementacje Pythona (takie jak Jython lub IronPython) mogą łatwo mieć inną implementację
id
.Więc do czego służy przypadek użycia
is
? PEP8 opisuje :Pytanie
Zadajesz i podajesz następujące pytanie (z kodem):
To nie jest oczekiwany wynik. Dlaczego się tego oczekuje? Oznacza to tylko, że liczby całkowite są wyceniane
256
przez obaa
ib
są tym samym wystąpienie całkowitej. Liczby całkowite są niezmienne w Pythonie, więc nie mogą się zmienić. Nie powinno to mieć wpływu na żaden kod. Nie należy się tego spodziewać. Jest to jedynie szczegół implementacji.Być może jednak powinniśmy się cieszyć, że nie ma nowej osobnej instancji w pamięci za każdym razem, gdy podamy wartość równą 256.
Wygląda na to, że mamy teraz dwa osobne wystąpienia liczb całkowitych o wartości
257
w pamięci. Ponieważ liczby całkowite są niezmienne, marnuje to pamięć. Miejmy nadzieję, że nie marnujemy dużo. Prawdopodobnie nie jesteśmy. Ale takie zachowanie nie jest gwarantowane.Wygląda na to, że twoja konkretna implementacja Pythona próbuje być inteligentna i nie tworzyć w pamięci liczb całkowitych nadmiarowych, chyba że jest to konieczne. Wygląda na to, że wskazujesz, że używasz referencyjnej implementacji Pythona, którą jest CPython. Dobry dla CPython.
Mogłoby być jeszcze lepiej, gdyby CPython mógł to zrobić globalnie, gdyby mógł to zrobić tanio (ponieważ poniosłoby to koszt w wyszukiwaniu), być może mogłaby to zrobić inna implementacja.
Ale jeśli chodzi o wpływ na kod, nie powinieneś się przejmować, czy liczba całkowita jest konkretnym wystąpieniem liczby całkowitej. Trzeba tylko dbać o wartość tego wystąpienia i do tego użylibyśmy zwykłych operatorów porównania, tj
==
.Co
is
robiis
sprawdza, czyid
dwa obiekty są takie same. W CPythonid
jest to lokalizacja w pamięci, ale może to być jakiś inny jednoznacznie identyfikujący numer w innej implementacji. Aby przekształcić to za pomocą kodu:jest taki sam jak
Dlaczego mielibyśmy chcieć użyć
is
?Może to być bardzo szybkie sprawdzenie w stosunku do powiedzenia, sprawdzenie, czy dwa bardzo długie ciągi mają równą wartość. Ponieważ jednak dotyczy to wyjątkowości obiektu, mamy dla niego ograniczone przypadki użycia. W rzeczywistości chcemy go najczęściej używać do sprawdzania
None
, czy jest to singleton (jedyna instancja istniejąca w jednym miejscu w pamięci). Możemy stworzyć inne singletony, jeśli istnieje możliwość ich połączenia, co możemy sprawdzićis
, ale są one stosunkowo rzadkie. Oto przykład (działa w Pythonie 2 i 3) npKtóre wydruki:
Widzimy więc, że za pomocą
is
i wartownika możemy rozróżnić, kiedybar
jest wywoływany bez argumentów, a kiedy jest wywoływany za pomocąNone
. Są to główne przypadki użycia dlais
- nie używaj go do testowania równości liczb całkowitych, ciągów, krotek lub innych podobnych rzeczy.źródło
is
- nie używaj go do testowania równości liczb całkowitych, ciągów, krotek lub innych podobnych rzeczy”. Próbuję jednak zintegrować prostą maszynę stanów z moją klasą, a ponieważ stany są nieprzejrzystymi wartościami, których jedyną możliwą do zaobserwowania właściwością jest to, że są identyczne lub różne, wygląda to całkiem naturalnie, aby były porównywalneis
. Planuję używać internowanych ciągów jako stanów. Wolałbym zwykłe liczby całkowite, ale niestety Python nie może internować liczb całkowitych (0 is 0
jest to szczegół implementacji).To zależy od tego, czy chcesz sprawdzić, czy 2 rzeczy są równe, czy ten sam obiekt.
is
sprawdza, czy są to ten sam obiekt, a nie tylko równe. Małe ints prawdopodobnie wskazują na to samo miejsce w pamięci dla wydajności przestrzeniNależy użyć
==
do porównania równości dowolnych obiektów. Możesz określić zachowanie za pomocą atrybutów__eq__
i__ne__
.źródło
Jestem spóźniony, ale chcesz jakieś źródło z odpowiedzią? Spróbuję to powiedzieć w sposób wprowadzający, aby więcej osób mogło śledzić.
Dobrą rzeczą w CPython jest to, że faktycznie możesz zobaczyć źródło tego. Użyję linków do wydania 3.5 , ale znajduję odpowiedni 2.x jest banalne.
W CPython jest funkcja C-API, która obsługuje tworzenie nowego
int
obiektuPyLong_FromLong(long v)
. Opis tej funkcji to:(Moje kursywa)
Nie wiem o tobie, ale widzę to i myślę: znajdźmy tę tablicę!
Jeśli nie bawiłeś się kodem C implementującym CPython , powinieneś ; wszystko jest dość uporządkowane i czytelne. Na naszym przypadku musimy patrzeć w
Objects
podkatalogu w drzewie katalogów głównego kodu źródłowego .PyLong_FromLong
zajmuje sięlong
przedmiotami, więc nie powinno być trudno wywnioskować, że musimy zajrzeć do środkalongobject.c
. Po wejściu do środka możesz pomyśleć, że wszystko jest chaotyczne; są, ale nie obawiaj się, funkcją, której szukamy, jest chłodzenie w linii 230, czekając, aż to sprawdzimy. Jest to niewielka funkcja, więc główny element (z wyłączeniem deklaracji) można łatwo wkleić tutaj:Nie jesteśmy już C -kodem-haxxorz, ale nie jesteśmy też głupi, widzimy, że
CHECK_SMALL_INT(ival);
zerkamy na nas uwodzicielsko; możemy zrozumieć, że ma to coś wspólnego z tym. Sprawdźmy to:Jest to makro wywołujące funkcję,
get_small_int
jeśli wartośćival
spełnia warunek:Więc czym są
NSMALLNEGINTS
iNSMALLPOSINTS
? Makra! Oto one :Więc naszym warunkiem jest
if (-5 <= ival && ival < 257)
połączenieget_small_int
.Następnie spójrzmy
get_small_int
w całej okazałości (cóż, po prostu spojrzymy na jego ciało, ponieważ tam są interesujące rzeczy):Ok, zadeklaruj
PyObject
, potwierdź, że poprzedni warunek utrzymuje i wykonaj przypisanie:small_ints
wygląda bardzo podobnie do tablicy, której szukaliśmy i jest! Mogliśmy po prostu przeczytać tę cholerną dokumentację i cały czas byśmy to wiedzieli! :Tak, to jest nasz facet. Gdy chcesz utworzyć nowy
int
w zakresie[NSMALLNEGINTS, NSMALLPOSINTS)
, po prostu otrzymasz odwołanie do już istniejącego obiektu, który został wstępnie przydzielony.Ponieważ odwołanie odnosi się do tego samego obiektu, wydanie
id()
bezpośrednio lub sprawdzenie tożsamościis
na nim zwróci dokładnie to samo.Ale kiedy są przydzielane?
Podczas inicjalizacji w
_PyLong_Init
Pythonie chętnie wejdzie w pętlę for, zrób to dla Ciebie:Sprawdź źródło, aby przeczytać treść pętli!
Mam nadzieję, że moje wyjaśnienie uczyniło cię teraz rzeczami C (gra słów wyraźnie zamierzona).
Ale
257 is 257
? Co tam?W rzeczywistości jest to łatwiejsze do wyjaśnienia i już próbowałem to zrobić ; wynika to z faktu, że Python wykona tę interaktywną instrukcję jako pojedynczy blok:
Podczas kompilacji tego stwierdzenia CPython zobaczy, że masz dwa pasujące literały i użyje tego samego
PyLongObject
reprezentowania257
. Możesz to zobaczyć, jeśli samodzielnie wykonasz kompilację i sprawdzisz jej zawartość:Kiedy CPython wykonuje operację, teraz po prostu ładuje dokładnie ten sam obiekt:
Więc
is
wróciTrue
.źródło
Jak można sprawdzić w pliku źródłowym intobject.c , Python buforuje małe liczby całkowite w celu zwiększenia wydajności. Za każdym razem, gdy tworzysz odwołanie do małej liczby całkowitej, odnosisz się do małej liczby całkowitej w pamięci podręcznej, a nie do nowego obiektu. 257 nie jest małą liczbą całkowitą, więc jest obliczana jako inny obiekt.
Lepiej jest używać
==
do tego celu.źródło
Myślę, że twoje hipotezy są prawidłowe. Eksperymentuj z
id
(tożsamością obiektu):Wygląda na to, że liczby
<= 255
są traktowane jak literały, a wszystko powyżej jest traktowane inaczej!źródło
W przypadku obiektów o niezmiennej wartości, takich jak liczby całkowite, ciągi lub czasy danych, tożsamość obiektu nie jest szczególnie przydatna. Lepiej myśleć o równości. Tożsamość jest zasadniczo szczegółem implementacji dla obiektów wartości - ponieważ są one niezmienne, nie ma efektywnej różnicy między posiadaniem wielu referencji do tego samego obiektu lub wielu obiektów.
źródło
Jest jeszcze jeden problem, który nie został wskazany w żadnej z istniejących odpowiedzi. Python może łączyć dowolne dwie niezmienne wartości, a wcześniej utworzone małe wartości int nie są jedynym sposobem, w jaki może się to zdarzyć. Implementacja Python nigdy nie gwarantuje tego, ale wszystkie robią to dla czegoś więcej niż tylko małych int.
Z jednej strony, istnieją inne wartości sprzed stworzony, takie jak pusta
tuple
,str
ibytes
, i niektórych krótkich ciągów (w CPython 3.6, to jest 256 jednoznakowe Latin-1 struny). Na przykład:Ale także nawet niepreparowane wartości mogą być identyczne. Rozważ te przykłady:
I nie ogranicza się to do
int
wartości:Oczywiście CPython nie ma wstępnie utworzonej
float
wartości dla42.23e100
. Co się tu dzieje?CPython kompilator połączyć stałych wartości pewnych znanych typów, takich jak, niezmiennych
int
,float
,str
,bytes
, w tej samej jednostce kompilacji. W przypadku modułu cały moduł jest jednostką kompilacji, ale w interaktywnym interpretatorze każda instrukcja jest osobną jednostką kompilacji. Ponieważc
id
są zdefiniowane w oddzielnych instrukcjach, ich wartości nie są scalane. Ponieważe
if
są zdefiniowane w tej samej instrukcji, ich wartości są scalane.Możesz zobaczyć, co się dzieje, demontując kod bajtowy. Spróbuj zdefiniować funkcję, która działa,
e, f = 128, 128
a następnie ją wywołajdis.dis
, a zobaczysz, że istnieje jedna stała wartość(128, 128)
Możesz zauważyć, że kompilator zapisał
128
jako stałą, mimo że tak naprawdę nie jest używany przez kod bajtowy, co daje wyobrażenie o tym, jak mało kompilacji robi kompilator CPython. Co oznacza, że (niepuste) krotki faktycznie nie są scalane:Umieścić, że w funkcji,
dis
to i spojrzenie naco_consts
-Jest to1
a2
, dwie(1, 2)
krotki, które mają te same1
, a2
jednak nie są identyczne, a((1, 2), (1, 2))
krotki, który ma dwa odrębne jednakowe krotki.Jest jeszcze jedna optymalizacja, którą robi CPython: internowanie ciągów. W przeciwieństwie do ciągłego składania kompilatora, nie ogranicza się to do literałów kodu źródłowego:
Z drugiej strony ogranicza się do
str
typu i ciągów pamięci wewnętrznej typu „ascii compact”, „compact” lub „legacy ready” , aw wielu przypadkach tylko „ascii compact” zostanie internowany.W każdym razie reguły dotyczące tego, jakie wartości muszą być, mogą być lub nie mogą być różne, różnią się w zależności od implementacji i między wersjami tej samej implementacji, a może nawet między uruchomieniami tego samego kodu na tej samej kopii tej samej implementacji .
Dla zabawy warto nauczyć się zasad jednego konkretnego Pythona. Ale nie warto na nich polegać w kodzie. Jedyną bezpieczną zasadą jest:
x is y
, używajx == y
)x is not y
, używajx != y
)Lub, innymi słowy, używaj tylko
is
do testowania udokumentowanych singletonów (jakNone
) lub które są tworzone tylko w jednym miejscu w kodzie (jak_sentinel = object()
idiom).źródło
x is y
do porównywania, używajx == y
. Podobnie nie używajx is not y
, używajx != y
a=257; b=257
w jednym wierszua is b
Prawdais
jest operatorem równości tożsamości (funkcjonującym jakid(a) == id(b)
); po prostu dwie równe liczby niekoniecznie są tym samym obiektem. Ze względu na wydajność niektóre małe liczby całkowite są zapamiętywane, więc zwykle będą takie same (można to zrobić, ponieważ są niezmienne).===
Z drugiej strony operator PHP opisany jest jako sprawdzanie równości i typu:x == y and type(x) == type(y)
zgodnie z komentarzem Paulo Freitasa. Będzie to wystarczające dla liczb wspólnych, ale różni się odis
klas, które definiują__eq__
w absurdalny sposób:Najwyraźniej PHP pozwala na to samo w przypadku klas „wbudowanych” (co mam na myśli zaimplementowanych na poziomie C, a nie w PHP). Nieco absurdalne użycie może być obiektem timera, który ma inną wartość za każdym razem, gdy jest używany jako liczba. Po prostu dlaczego chcesz emulować Visual Basic
Now
zamiast pokazywać, że jest to ewaluacja, którejtime.time()
nie wiem.Greg Hewgill (OP) skomentował: „Moim celem jest porównanie tożsamości obiektu, a nie równości wartości. Z wyjątkiem liczb, w których chcę traktować tożsamość obiektu tak samo, jak równość wartości”.
To byłaby jeszcze jedna odpowiedź, ponieważ musimy kategoryzować rzeczy jako liczby, czy nie, aby wybrać, czy porównamy z
==
czyis
. CPython definiuje protokół liczbowy , w tym PyNumber_Check, ale nie jest to dostępne z poziomu samego Pythona.Możemy spróbować użyć
isinstance
wszystkich znanych nam typów liczb, ale nieuchronnie byłoby to niepełne. Moduł typów zawiera listę StringTypes, ale nie ma NumberTypes. Od wersji Python 2.6 wbudowane klasy liczb mają klasę podstawowąnumbers.Number
, ale ma ten sam problem:Nawiasem mówiąc, NumPy produkuje osobne przypadki niskich liczb.
Tak naprawdę nie znam odpowiedzi na ten wariant pytania. Przypuszczam, że teoretycznie można by wywoływać ctypy
PyNumber_Check
, ale nawet ta funkcja była dyskutowana i na pewno nie jest przenośna. Będziemy musieli być mniej konkretni w kwestii tego, na co teraz testujemy.Ostatecznie ten problem wynika z tego, że Python nie miał pierwotnie drzewa typów z predykatami takimi jak Scheme
number?
lub Haskell typ klasy Num .is
sprawdza tożsamość obiektu, a nie równość wartości. PHP ma również kolorową historię, w której===
najwyraźniej zachowuje się jakis
tylko na obiektach w PHP5, ale nie PHP4 . Takie są rosnące problemy z poruszaniem się po różnych językach (w tym wersjach jednego).źródło
Dzieje się tak również z łańcuchami:
Teraz wszystko wydaje się w porządku.
Tego też się spodziewamy.
To nieoczekiwane.
źródło
'xx'
jest zgodny z oczekiwaniami'xxx'
, ale'x x'
nie jest.xx
w sesji Pythona jest coś nazwanego , ten ciąg jest już internowany; i może istnieć heurystyka, która to robi, jeśli tylko przypomina nazwę. Podobnie jak w przypadku liczb, można to zrobić, ponieważ są one niezmienne. docs.python.org/2/library/functions.html#intern guilload.com/python-string-interningCo nowego w Python 3.8: Zmiany w zachowaniu Python :
źródło