2016-07-07 34 views
7

Nella risposta this ho trovato un modo per ottenere un conteggio dei riferimenti degli oggetti in Python.Perché i riferimenti di Python agli interi piccoli sono sorprendentemente alti?

Hanno menzionato utilizzando sys.getrefcount(). L'ho provato, ma sto ottenendo un risultato inaspettato. Quando c'è 1 riferimento, sembra che il conteggio sia 20. Perché è così?

Ho guardato il documentation ma non sembra spiegare il motivo.

enter image description here

+1

Provare 'sys.getrefcount (257)' e probabilmente diminuirà notevolmente. –

+0

Prova 'sys.getrefcount (None)' per un numero interessante. – cdarke

+1

Questo è davvero strano, non ho mai creato una var con 257. Perché restituisce 3? –

risposta

5

Tale scopo succede ad avere 20 riferimenti ad esso al momento della prima sys.getrefcount chiamata. Non sono solo i riferimenti che hai creato; ci sono tutti i tipi di altri riferimenti ad esso in altri moduli e negli interni di Python, poiché (questo è un dettaglio di implementazione) l'implementazione standard di Python crea solo un oggetto 100 e lo usa per tutte le occorrenze di 100 in un programma Python.

+0

Interessante, buono a sapersi, grazie! –

+1

In modo più pertinente, se crei i tuoi oggetti (elenchi, tuple, istanze di classe), otterrai il comportamento previsto. – alexis

5

Ci sono un sacco di motivi per avere molti riferimenti a un oggetto. Rintracciare quale può essere difficile, e decidere se ne vale la pena può ignorare il livello di interesse. Il conteggio dei riferimenti è di interesse primario per gli sviluppatori di applicazioni di debug e varianti di python.

  • Python tenta di mantenere un solo valore effettivo per ogni riferimento. Quindi, il 100 che hai nel tuo esempio sarebbe lo stesso 100 che è un limite interno alle chiamate di ricorsione o lo stesso 100 di un indice di loop corrente.
  • Python mantiene riferimenti extra ad alcuni oggetti comuni, compresi gli interi bassi. Il numero di riferimento a 1,234,567 è diverso dal conteggio a 20.
  • Molte funzioni memorizzano e mantengono riferimenti a argomenti recenti.
  • Alcuni interpreti mantengono riferimenti a valori e valori recenti a cui fanno riferimento le righe recenti. Ad esempio, il precedente valore di ritorno è memorizzato in "_". Ciò significa che l'esecuzione nell'interprete e l'esecuzione dalla riga di comando darà risposte diverse.
  • Come tutti gli schemi di conteggio dei riferimenti, ci sono errori. Ad esempio, PyTuple_GetItem() ha avuto alcune scelte discutibili.

Il numero esatto di conteggi e significati di tali conteggi sarà diverso in PyPi rispetto a C-Python rispetto a IPython. Il conteggio dei riferimenti è raramente un buon strumento per trovare un comportamento strano in Python.

4

È divertente leggere il codice sorgente di Python2.7 che è molto ben scritto e chiaro da leggere. (Mi riferisco alla versione 2.7.12 se vuoi giocare a casa.) Un buon punto di partenza per capire il codice è l'eccellente serie di lezioni: C Python Internals che parte dal punto di vista di un principiante.

Il codice critico (scritto in C) relativo a noi appare nel file "Objects/intobject.c" (Ho rimosso un po 'di codice #ifdef e leggermente modificato la creazione di un nuovo oggetto Integer per chiarezza):

#define NSMALLPOSINTS   257 
    #define NSMALLNEGINTS   5 
    static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; 

    PyObject * 
    PyInt_FromLong(long ival) 
    { 
     register PyIntObject *v; 
     if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { 
      v = small_ints[ival + NSMALLNEGINTS]; 
      Py_INCREF(v); 
      return (PyObject *) v; 
     } 
     /* Inline PyObject_New */ 
     v = (PyIntObject *)Py_TYPE(v); 
     PyObject_INIT(v, &PyInt_Type); 
     v->ob_ival = ival; 
     return (PyObject *) v; 
    } 

Quindi, in sostanza, crea un array preimpostato contenente tutti i numeri compresi tra -5 e 256 e utilizza tali oggetti (aumentando il numero di riferimenti usando la macro Py_INCREF) se possibile. In caso contrario, creerà un oggetto PyInt_Type nuovo di zecca, che viene inizializzato con un conteggio di riferimento pari a 1.

Il mistero del motivo per cui ogni numero sembra avere un conteggio di riferimento di 3 (in realtà quasi qualsiasi nuovo oggetto) viene rivelato solo quando si guarda il codice byte generato da Python. La Macchina Virtuale opera con uno stack di valori (un po 'come in Forth), e ogni volta che un oggetto viene posto sullo stack di valori, incrementa il conteggio dei riferimenti.

Quindi quello che sospetto che stia accadendo è che il tuo codice fornisce di per sé tutti e 3 i riferimenti che vedi, poiché per i numeri che non sono nell'elenco di valori piccoli, dovresti ottenere un oggetto unico. Il primo riferimento è presumibilmente nello stack di valori per il chiamante di getrefcount quando effettua la chiamata; il secondo è nell'elenco delle variabili locali per il frame getrefcount; il terzo è probabile sullo stack di valori nel frame getrefcount mentre cerca il suo conteggio dei riferimenti.

Uno strumento utile se si desidera approfondire ulteriormente il problema sono il comando 'compile' e il comando 'dis' (disassemblare) che si trova nel modulo 'dis', che insieme consentiranno di leggere l'effettivo codice byte generato da qualsiasi parte del codice Python e dovrebbe aiutarti a scoprire esattamente quando e dove viene creato il terzo riferimento.

Per quanto riguarda i conteggi di riferimento più elevati per valori piccoli, quando si avvia Python, carica automaticamente l'intera libreria standard e viene eseguito un bel po 'di codice di inizializzazione del modulo Python prima di iniziare a interpretare il proprio codice. Questi moduli contengono le proprie copie di molti degli interi piccoli (e dell'oggetto None che è anche unico).