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.