Python memorizza nella cache gli interi nell'intervallo [-5, 256]
, pertanto è previsto che anche gli interi in tale intervallo siano identici.
Quello che vedi è il compilatore Python che ottimizza i letterali identici quando fa parte dello stesso testo.
Quando si digita nel Python sborsare ogni riga è una dichiarazione completamente diversa, analizzato in un momento diverso, in tal modo:
>>> a = 257
>>> b = 257
>>> a is b
False
Ma se si mette lo stesso codice in un file:
$ echo 'a = 257
> b = 257
> print a is b' > testing.py
$ python testing.py
True
Questo accade ogni volta che il parser ha la possibilità di analizzare dove vengono utilizzati i letterali, ad esempio quando si definisce una funzione nell'interprete interattivo:
>>> def test():
... a = 257
... b = 257
... print a is b
...
>>> dis.dis(test)
2 0 LOAD_CONST 1 (257)
3 STORE_FAST 0 (a)
3 6 LOAD_CONST 1 (257)
9 STORE_FAST 1 (b)
4 12 LOAD_FAST 0 (a)
15 LOAD_FAST 1 (b)
18 COMPARE_OP 8 (is)
21 PRINT_ITEM
22 PRINT_NEWLINE
23 LOAD_CONST 0 (None)
26 RETURN_VALUE
>>> test()
True
>>> test.func_code.co_consts
(None, 257)
Nota come il codice compilato contiene una singola costante per 257
.
In conclusione, il compilatore bytecode Python non è in grado di eseguire massicce ottimizzazioni (come i linguaggi di tipi statici), ma fa più di quanto si pensi. Una di queste cose è analizzare l'uso dei letterali ed evitare di duplicarli.
Si noti che questo non ha a che fare con la cache, perché funziona anche per i carri, che non hanno una cache:
>>> a = 5.0
>>> b = 5.0
>>> a is b
False
>>> a = 5.0; b = 5.0
>>> a is b
True
Per letterali più complessi, come le tuple, "non lo fa lavoro ":
>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a = (1,2); b = (1,2)
>>> a is b
False
Ma i letterali all'interno della tupla sono condivisi:
>>> a = (257, 258)
>>> b = (257, 258)
>>> a[0] is b[0]
False
>>> a[1] is b[1]
False
>>> a = (257, 258); b = (257, 258)
>>> a[0] is b[0]
True
>>> a[1] is b[1]
True
Per quanto riguarda il motivo per cui vengono creati due PyInt_Object
, vorrei indovinare per evitare un confronto letterale. per esempio, il numero 257
può essere espressa da più letterali:
>>> 257
257
>>> 0x101
257
>>> 0b100000001
257
>>> 0o401
257
Il parser ha due scelte:
- convertire i letterali in qualche base comune prima di creare il numero intero, e vedere se i letterali sono equivalente. quindi creare un singolo oggetto intero.
- Creare gli oggetti interi e vedere se sono uguali. Se sì, mantieni solo un singolo valore e assegnalo a tutti i valori letterali, altrimenti hai già gli interi da assegnare.
Probabilmente il parser Python utilizza il secondo approccio, che evita di riscrivere il codice di conversione ed è anche più semplice da estendere (ad esempio funziona anche con i float).
Lettura del file Python/ast.c
, la funzione che analizza tutti i numeri è parsenumber
, che chiama PyOS_strtoul
per ottenere il valore intero (per intgers) e alla fine chiama PyLong_FromString
:
x = (long) PyOS_strtoul((char *)s, (char **)&end, 0);
if (x < 0 && errno == 0) {
return PyLong_FromString((char *)s,
(char **)0,
0);
}
Come si può vedere qui il parser fa non controlla se ha già trovato un intero con il valore dato e questo spiega perché vedi che due oggetti int sono creati, e questo significa anche che la mia ipotesi era corretta: il parser t crea le costanti e solo successivamente ottimizza il bytecode per utilizzare lo stesso oggetto per costanti uguali.
Il codice che esegue questo controllo deve essere da qualche parte in Python/compile.c
o Python/peephole.c
, poiché questi sono i file che trasformano l'AST in bytecode.
In particolare, la funzione compiler_add_o
sembra quella che lo fa. C'è questo commento in compiler_lambda
:
/* Make None the first constant, so the lambda can't have a
docstring. */
if (compiler_add_o(c, c->u->u_consts, Py_None) < 0)
return 0;
Così sembra compiler_add_o
viene utilizzato per inserire costanti per le funzioni/lambda, ecc La funzione compiler_add_o
memorizza le costanti in un oggetto dict
, e da questo consegue che subito uguali costanti cadrà nello stesso slot, risultando in una singola costante nel bytecode finale.
Stai solo cercando di capire come funziona il sorgente Python o stai cercando di capire qual è il risultato del codice scritto in Python? Poiché il risultato del codice scritto in Python è "questo è un dettaglio dell'implementazione, non contare mai sul fatto che ciò accada o non accada". – BrenBarn
Non ho intenzione di fare affidamento sui dettagli di implementazione. Sono solo curioso e cerco di entrare nel codice sorgente. – felix021
Correlato: [Python "è" l'operatore si comporta in modo imprevisto con numeri interi] (http://stackoverflow.com/questions/306313/python-is-operator-behaves-unexpectedly-with-integers) – Blckknght