Sono in ritardo ma, vuoi una fonte con la tua risposta? Cercherò di esprimerlo in modo introduttivo in modo che più persone possano seguirlo.
Un aspetto positivo di CPython è che puoi effettivamente vedere la fonte per questo. Userò i collegamenti per la versione 3.5 , ma trovo il corrispondente 2.x è banale.
In CPython, il C-API che gestisce la creazione di un nuovo int
oggetto è PyLong_FromLong(long v)
. La descrizione per questa funzione è:
L'implementazione corrente mantiene un array di oggetti interi per tutti i numeri interi compresi tra -5 e 256, quando si crea un int in quell'intervallo in realtà si ottiene semplicemente un riferimento all'oggetto esistente . Quindi dovrebbe essere possibile modificare il valore di 1. Sospetto che il comportamento di Python in questo caso non sia definito. :-)
(Il mio corsivo)
Non ti conosco ma lo vedo e penso: troviamo quell'array!
Se non hai armeggiato con il codice C che implementa CPython dovresti ; tutto è abbastanza organizzato e leggibile. Nel nostro caso, dobbiamo guardare nella Objects
sottodirectory dell'albero principale del codice sorgente .
PyLong_FromLong
tratta gli long
oggetti, quindi non dovrebbe essere difficile dedurre che dobbiamo sbirciare dentro longobject.c
. Dopo aver guardato dentro potresti pensare che le cose siano caotiche; sono, ma non temono, la funzione che stiamo cercando è agghiacciante alla linea 230 che ci aspetta per verificarlo. È una funzione piccola, quindi il corpo principale (escluse le dichiarazioni) è facilmente incollato qui:
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;
}
Ora, non siamo C master-code-haxxorz ma non siamo nemmeno stupidi, possiamo vederlo CHECK_SMALL_INT(ival);
sbirciare tutti in modo seducente; possiamo capire che ha qualcosa a che fare con questo. Controlliamolo:
#define CHECK_SMALL_INT(ival) \
do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
return get_small_int((sdigit)ival); \
} while(0)
Quindi è una macro che chiama la funzione get_small_int
se il valore ival
soddisfa la condizione:
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)
Quindi cosa sono NSMALLNEGINTS
e NSMALLPOSINTS
? Macro! Eccoli :
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
Quindi la nostra condizione è if (-5 <= ival && ival < 257)
chiamata get_small_int
.
Ora diamo un'occhiata a get_small_int
tutta la sua gloria (beh, vedremo solo il suo corpo perché è lì che sono le cose interessanti):
PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
Va bene, dichiarare a PyObject
, affermare che la condizione precedente è valida ed eseguire l'incarico:
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
small_ints
assomiglia molto all'array che stavamo cercando, ed è così! Avremmo potuto leggere la dannata documentazione e lo avremmo saputo da sempre! :
/* 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];
Quindi sì, questo è il nostro ragazzo. Quando vuoi crearne uno nuovo int
nell'intervallo[NSMALLNEGINTS, NSMALLPOSINTS)
si ottiene semplicemente un riferimento a un oggetto già esistente che è stato preallocato.
Poiché il riferimento si riferisce allo stesso oggetto, emette id()
direttamente o verifica l'identità conis
su restituirà esattamente la stessa cosa.
Ma quando vengono assegnati ??
Durante l'inizializzazione in_PyLong_Init
Python entrerò volentieri in un ciclo for, fallo per te:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
Controlla la fonte per leggere il corpo del loop!
Spero che la mia spiegazione ti abbia reso C cose chiaramente ora (gioco di parole ovviamente inteso).
Ma 257 is 257
? Che cosa succede?
Questo è in realtà più facile da spiegare, e ho già provato a farlo ; è dovuto al fatto che Python eseguirà questa istruzione interattiva come un singolo blocco:
>>> 257 is 257
Durante la compilazione di questa affermazione, CPython vedrà che hai due letterali corrispondenti e userà la stessa PyLongObject
rappresentazione 257
. Puoi vederlo se esegui la compilazione da solo e ne esamini il contenuto:
>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)
Quando CPython esegue l'operazione, ora caricherà esattamente lo stesso oggetto:
>>> import dis
>>> dis.dis(codeObj)
1 0 LOAD_CONST 0 (257) # dis
3 LOAD_CONST 0 (257) # dis again
6 COMPARE_OP 8 (is)
Quindi is
tornerà True
.