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
obiektu 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 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 ś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_int
jeśli wartość ival
spełnia warunek:
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)
Więc czym są NSMALLNEGINTS
i 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_int
w 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_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! :
/* 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 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:
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 PyLongObject
reprezentowania 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 is
wróci True
.