Operator „jest” zachowuje się nieoczekiwanie przy liczbach całkowitych

509

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

Greg Hewgill
źródło
1
Spójrz tutaj > Obecna implementacja utrzymuje tablicę liczb całkowitych dla wszystkich> liczb całkowitych od -5 do 256, kiedy tworzysz liczbę całkowitą w tym zakresie, po prostu odzyskujesz odniesienie do istniejącego obiektu.
user5319825
2
Jest to szczegół implementacji specyficzny dla CPython i nieokreślone zachowanie, należy
zachować

Odpowiedzi:

392

Popatrz na to:

>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828

Oto, co znalazłem w dokumentacji Python 2, „Plain Integer Objects” (to samo dotyczy Python 3 ):

Obecna implementacja utrzymuje tablicę obiektów liczb całkowitych dla wszystkich liczb całkowitych od -5 do 256, kiedy tworzysz liczbę całkowitą w tym zakresie, w rzeczywistości po prostu odzyskujesz odniesienie do istniejącego obiektu. Dlatego powinna istnieć możliwość zmiany wartości 1. Podejrzewam, że zachowanie Pythona w tym przypadku jest niezdefiniowane. :-)

Cybis
źródło
46
czy ktoś wie, jak wybrano ten zakres (-5, 256)? nie byłbym zaskoczony, gdyby to było (0, 255), a nawet (-255, 255), ale zakres 262 liczb zaczynających się od -5 wydaje się zaskakująco arbitralny.
Woodrow Barlow
6
@WoodrowBarlow: Myślę, że -5 to tylko heurystyka do przechwytywania wspólnych negatywnych symboli zastępczych. 0..255 obejmuje tablice wartości jednobajtowych. Jest to 256, co jest tajemnicze, ale myślę, że służy do (de) składania liczb całkowitych w / z bajtów.
Davis Herring
3
Z tego, co rozumiem, zakres został wybrany, patrząc na powszechnie używane wartości w wielu projektach (i wielu językach).
Tony Suffolk 66
9
Według reddit.com/r/Python/comments/18leav/… , zakres wynosił kiedyś [-5,100]. Został rozszerzony o pełny zakres wartości bajtów - plus 256, ponieważ jest to prawdopodobnie liczba wspólna.
mwfearnley
2
@Ashwani spróbuj przeczytać komentarze tuż obok komentarza, opublikowane dwa lata wcześniej, a znajdziesz odpowiedź na swoje pytanie.
jbg
116

Operator „czy” Pythona zachowuje się nieoczekiwanie przy liczbach całkowitych?

Podsumowując - podkreślę: nie używaj isdo 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:

>>> a = 1000
>>> a == 1000       # Test integers like this,
True
>>> a != 5000       # or this!
True
>>> a is 1000       # Don't do this! - Don't use `is` to test integers!!
False

Wyjaśnienie

Aby to wiedzieć, musisz wiedzieć, co następuje.

Po pierwsze, co robi is ? Jest operatorem porównania. Z dokumentacji :

Operatorzy isiis not test tożsamości obiektu: x is yjest prawdą, jeśli i tylko wtedy, gdy xiy są tym samym obiektem. x is not ydaje odwrotną wartość prawdy.

A zatem poniższe są równoważne.

>>> a is b
>>> id(a) == id(b)

Z dokumentacji :

id Zwraca „tożsamość” obiektu. Jest to liczba całkowita (lub długa liczba całkowita), która gwarantuje, że będzie unikalna i stała dla tego obiektu przez cały okres jego użytkowania. Dwa obiekty z nie nakładającymi się okresami życia mogą mieć to samoid() wartość.

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 :

Porównania z singletonami, takie jak, Nonenależy zawsze przeprowadzać z operatorami równości islub is notnigdy.

Pytanie

Zadajesz i podajesz następujące pytanie (z kodem):

Dlaczego poniższe zachowania zachowują się nieoczekiwanie w Pythonie?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result

To nie jest oczekiwany wynik. Dlaczego się tego oczekuje? Oznacza to tylko, że liczby całkowite są wyceniane 256przez obaa i bsą 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.

>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?

Wygląda na to, że mamy teraz dwa osobne wystąpienia liczb całkowitych o wartości 257w 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.

>>> 257 is 257
True           # Yet the literal numbers compare properly

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 robi

issprawdza, czy iddwa obiekty są takie same. W CPython idjest 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:

>>> a is b

jest taki sam jak

>>> id(a) == id(b)

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

SENTINEL_SINGLETON = object() # this will only be created one time.

def foo(keyword_argument=None):
    if keyword_argument is None:
        print('no argument given to foo')
    bar()
    bar(keyword_argument)
    bar('baz')

def bar(keyword_argument=SENTINEL_SINGLETON):
    # SENTINEL_SINGLETON tells us if we were not passed anything
    # as None is a legitimate potential argument we could get.
    if keyword_argument is SENTINEL_SINGLETON:
        print('no argument given to bar')
    else:
        print('argument to bar: {0}'.format(keyword_argument))

foo()

Które wydruki:

no argument given to foo
no argument given to bar
argument to bar: None
argument to bar: baz

Widzimy więc, że za pomocą isi wartownika możemy rozróżnić, kiedy barjest wywoływany bez argumentów, a kiedy jest wywoływany za pomocą None. Są to główne przypadki użycia dla is- nie używaj go do testowania równości liczb całkowitych, ciągów, krotek lub innych podobnych rzeczy.

Aaron Hall
źródło
„Są to główne przypadki użycia 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ównywalne is. 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 0jest to szczegół implementacji).
Alexey
@Alexey brzmi, jakbyś potrzebował wyliczeń? stackoverflow.com/questions/37601644/…
Aaron Hall
Może dzięki, nie wiedziałem o nich. Może to być odpowiedni dodatek do odpowiedzi IMO.
Alexey
Być może użycie w odpowiedzi kilku głupich obiektów, takich jak wartownik, byłoby bardziej lekkim rozwiązaniem ...
Alexey
Wyliczenia @Alexey znajdują się w standardowej bibliotece Python 3, a to prawdopodobnie zachęciłoby twój kod do bardziej znaczącego znaczenia niż zwykłe wartowniki.
Aaron Hall
60

To zależy od tego, czy chcesz sprawdzić, czy 2 rzeczy są równe, czy ten sam obiekt.

issprawdza, 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 przestrzeni

In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144

Należy użyć ==do porównania równości dowolnych obiektów. Możesz określić zachowanie za pomocą atrybutów __eq__i __ne__.

JimB
źródło
Kciuki w górę za wyjaśnienie, jak porównać dowolne obiekty, tak jak poprosił PO !!
Joooeey
54

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 intobiektu PyLong_FromLong(long v). Opis tej funkcji to:

Obecna implementacja utrzymuje tablicę obiektów liczb całkowitych dla wszystkich liczb całkowitych od -5 do 256, kiedy tworzysz liczbę całkowitą w tym zakresie, w rzeczywistości po prostu odzyskujesz odniesienie do istniejącego obiektu . Dlatego powinna istnieć możliwość zmiany wartości 1. Podejrzewam, że zachowanie Pythona w tym przypadku jest niezdefiniowane. :-)

(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 Objectspodkatalogu w drzewie katalogów głównego kodu źródłowego .

PyLong_FromLongzajmuje się longprzedmiotami, więc nie powinno być trudno wywnioskować, że musimy zajrzeć do środka longobject.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:

PyObject *
PyLong_FromLong(long ival)
{
    // omitting declarations

    CHECK_SMALL_INT(ival);

    if (ival < 0) {
        /* negate: cant write this as abs_ival = -ival since that
           invokes undefined behaviour when ival is LONG_MIN */
        abs_ival = 0U-(unsigned long)ival;
        sign = -1;
    }
    else {
        abs_ival = (unsigned long)ival;
    }

    /* Fast path for single-digit ints */
    if (!(abs_ival >> PyLong_SHIFT)) {
        v = _PyLong_New(1);
        if (v) {
            Py_SIZE(v) = sign;
            v->ob_digit[0] = Py_SAFE_DOWNCAST(
                abs_ival, unsigned long, digit);
        }
        return (PyObject*)v; 
}

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:

#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } while(0)

Jest to makro wywołujące funkcję, get_small_intjeśli wartość ivalspełnia warunek:

if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)

Więc czym są NSMALLNEGINTSi NSMALLPOSINTS? Makra! Oto one :

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

Więc naszym warunkiem jest if (-5 <= ival && ival < 257)połączenie get_small_int.

Następnie spójrzmy get_small_intw całej okazałości (cóż, po prostu spojrzymy na jego ciało, ponieważ tam są interesujące rzeczy):

PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);

Ok, zadeklaruj PyObject, potwierdź, że poprzedni warunek utrzymuje i wykonaj przypisanie:

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

small_intswyglą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! :

/* Small integers are preallocated in this array so that they
   can be shared.
   The integers that are preallocated are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

Tak, to jest nasz facet. Gdy chcesz utworzyć nowy intw 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:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {

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:

>>> 257 is 257

Podczas kompilacji tego stwierdzenia CPython zobaczy, że masz dwa pasujące literały i użyje tego samego PyLongObjectreprezentowania 257. Możesz to zobaczyć, jeśli samodzielnie wykonasz kompilację i sprawdzisz jej zawartość:

>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)

Kiedy CPython wykonuje operację, teraz po prostu ładuje dokładnie ten sam obiekt:

>>> import dis
>>> dis.dis(codeObj)
  1           0 LOAD_CONST               0 (257)   # dis
              3 LOAD_CONST               0 (257)   # dis again
              6 COMPARE_OP               8 (is)

Więc iswróci True.

Dimitris Fasarakis Hilliard
źródło
37

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.

Anioł
źródło
19

Myślę, że twoje hipotezy są prawidłowe. Eksperymentuj z id(tożsamością obiektu):

In [1]: id(255)
Out[1]: 146349024

In [2]: id(255)
Out[2]: 146349024

In [3]: id(257)
Out[3]: 146802752

In [4]: id(257)
Out[4]: 148993740

In [5]: a=255

In [6]: b=255

In [7]: c=257

In [8]: d=257

In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)

Wygląda na to, że liczby <= 255są traktowane jak literały, a wszystko powyżej jest traktowane inaczej!

Amit
źródło
1
Wynika to z faktu, że obiekty reprezentujące wartości od -5 do +256 są tworzone w czasie uruchamiania - a więc wszystkie te wartości wykorzystują do wcześniej zbudowanego obiektu. Prawie wszystkie odwołania do liczb całkowitych poza tym zakresem tworzą nowy obiekt wewnętrzny za każdym razem, gdy się do nich odwołuje. Myślę, że użycie terminu literał jest mylące - literał zwykle odnosi się do każdej wartości wpisanej w kodzie - więc wszystkie liczby w kodzie źródłowym są literałami.
Tony Suffolk 66
13

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.

babbageclunk
źródło
12

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, stri bytes, i niektórych krótkich ciągów (w CPython 3.6, to jest 256 jednoznakowe Latin-1 struny). Na przykład:

>>> a = ()
>>> b = ()
>>> a is b
True

Ale także nawet niepreparowane wartości mogą być identyczne. Rozważ te przykłady:

>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True

I nie ogranicza się to do intwartości:

>>> g, h = 42.23e100, 42.23e100
>>> g is h
True

Oczywiście CPython nie ma wstępnie utworzonej floatwartoś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ż ci dsą zdefiniowane w oddzielnych instrukcjach, ich wartości nie są scalane. Ponieważ ei fsą 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, 128a następnie ją wywołaj dis.dis, a zobaczysz, że istnieje jedna stała wartość(128, 128)

>>> def f(): i, j = 258, 258
>>> dis.dis(f)
  1           0 LOAD_CONST               2 ((128, 128))
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (i)
              6 STORE_FAST               1 (j)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> f.__code__.co_consts
(None, 128, (128, 128))
>>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1])
4305296480, 4305296480, 4305296480

Możesz zauważyć, że kompilator zapisał 128jako 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:

>>> k, l = (1, 2), (1, 2)
>>> k is l
False

Umieścić, że w funkcji, disto i spojrzenie na co_consts-Jest to 1a 2, dwie (1, 2)krotki, które mają te same 1, a 2jednak 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:

>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True

Z drugiej strony ogranicza się do strtypu 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:

  • Nie pisz kodu, który zakłada, że ​​dwie równe, ale oddzielnie utworzone niezmienne wartości są identyczne (nie używaj x is y, używaj x == y)
  • Nie pisz kodu, który zakłada, że ​​dwie równe, ale oddzielnie utworzone niezmienne wartości są różne (nie używaj x is not y, używaj x != y)

Lub, innymi słowy, używaj tylko isdo testowania udokumentowanych singletonów (jak None) lub które są tworzone tylko w jednym miejscu w kodzie (jak _sentinel = object()idiom).

abarnert
źródło
Mniej tajemnicza rada to po prostu: nie używaj x is ydo porównywania, używaj x == y. Podobnie nie używaj x is not y, używajx != y
smci
Patrząc na to pytanie , dlaczego jest a=257; b=257w jednym wierszu a is bPrawda
Joe
8

is jest operatorem równości tożsamości (funkcjonującym jak id(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ę od isklas, które definiują __eq__w absurdalny sposób:

class Unequal:
    def __eq__(self, other):
        return False

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 Nowzamiast pokazywać, że jest to ewaluacja, której time.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 ==czy is. CPython definiuje protokół liczbowy , w tym PyNumber_Check, ale nie jest to dostępne z poziomu samego Pythona.

Możemy spróbować użyć isinstancewszystkich 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:

import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)

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 . issprawdza tożsamość obiektu, a nie równość wartości. PHP ma również kolorową historię, w której ===najwyraźniej zachowuje się jak istylko na obiektach w PHP5, ale nie PHP4 . Takie są rosnące problemy z poruszaniem się po różnych językach (w tym wersjach jednego).

Yann Vernier
źródło
4

Dzieje się tak również z łańcuchami:

>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Teraz wszystko wydaje się w porządku.

>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Tego też się spodziewamy.

>>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, True, 4555308080, 4555308080)

>>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, False, 4555308176, 4555308272)

To nieoczekiwane.

sobolevn
źródło
Stało się to - zgodziłem się, że jeszcze dziwniejsze. Więc bawiłem się tym, a to jest jeszcze dziwniejsze - związane z przestrzenią. Na przykład ciąg znaków 'xx'jest zgodny z oczekiwaniami 'xxx', ale 'x x'nie jest.
Brian
2
To dlatego, że wygląda jak symbol, jeśli nie ma w nim miejsca. Nazwy są automatycznie internowane, więc jeśli xxw 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-interning
Yann Vernier
3

Co nowego w Python 3.8: Zmiany w zachowaniu Python :

Kompilator generuje teraz SyntaxWarning, gdy kontrole tożsamości ( isi is not) są używane z pewnymi typami literałów (np. Ciągi, ints). Mogą często działać przypadkowo w CPython, ale nie gwarantuje ich specyfikacja języka. Ostrzeżenie zaleca użytkownikom korzystanie z testów równości ( == i !=) zamiast.

cclauss
źródło