gc - Garbage Collector

Scopo Gestisce la memoria usata dagli oggetti Python
Versione Python 2.1 e superiore

Il modulo gc espone il sottostante meccanismo di gestione della memoria di Python, il garbage collector automatico. Il modulo include funzioni per controllare come il collettore opera e per esaminare gli oggetti noti al sistema, siano essi insiemi in sospeso o cicli di riferimenti bloccati che non sono in grado di essere liberati.

Tracciare i Riferimenti

Con gc si possono utilizzare i riferimenti in uscita ed in entrata tra oggetti per trovare cicli in strutture dati complesse. Se si conosce la struttura dati con il ciclo, è possibile costruire del codice personalizzato per esaminarne le proprietà. Altrimenti è possibile utilizzare le funzioni get_referents() e get_referrers per costruire strumenti generici di debug.

Ad esempio get_referents() mostra gli oggetti referenziati dagli argomenti in input

import gc
import pprint

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
    def set_next(self, next):
        print 'Collegamento nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)

# Costruisce un ciclo di Grafi
one = Graph('uno')
two = Graph('due')
three = Graph('tre')
one.set_next(two)
two.set_next(three)
three.set_next(one)

print
print 'tre si riferisce a:'
for r in gc.get_referents(three):
    pprint.pprint(r)

In questo caso l'instanza three di Graph ha un riferimento al suo dizionario di istanze (nell'attributo __dict__) ed alla sua classe

$ python gc_get_referents.py

Collegamento nodi Graph(uno).next = Graph(due)
Collegamento nodi Graph(due).next = Graph(tre)
Collegamento nodi Graph(tre).next = Graph(uno)

tre si riferisce a:
{'name': 'tre', 'next': Graph(uno)}

Questo esempio utilizza una Queue per eseguire una ricerca in ampiezza di tutti i riferimenti di oggetto per trovare dei cicli. Gli elementi inseriti nella queue sono tuple che contengono la catena dei riferimenti fino ad ora ed il prossimo oggetto da esaminare. Inizia con three, poi cerca qualsiasi cosa ad esso faccia riferimento. L'ignorare le classi consente evitare di cercare tra metodi, moduli, ecc.

import gc
import pprint
import Queue

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
    def set_next(self, next):
        print 'Collegamento nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)

# Costruisce un ciclo di Grafi
one = Graph('uno')
two = Graph('due')
three = Graph('tre')
one.set_next(two)
two.set_next(three)
three.set_next(one)

print

seen = set()
to_process = Queue.Queue()

# Si parte con una catena di oggetti vuota ed il Grafo three.
to_process.put( ([], three) )

# Cerca dei cicli, costruendo la catena di oggetti per ogni oggetto che
# viene trovato nella coda in modo che si possa stampare l'intero ciclo quando
# si finisce
while not to_process.empty():
    chain, next = to_process.get()
    chain = chain[:]
    chain.append(next)
    print 'In esame:', repr(next)
    seen.add(id(next))
    for r in gc.get_referents(next):
        if isinstance(r, basestring) or isinstance(r, type):
            # Ignora stringhe e classi
            pass
        elif id(r) in seen:
            print
            print 'Trovato un ciclo per %s:' % r
            for i, link in enumerate(chain):
                print '  %d: ' % i,
                pprint.pprint(link)
        else:
            to_process.put( (chain, r) )

Il ciclo nei nodi viene facilmente trovato cercando oggetti che sono già stati esaminati. Per evitare di mantenere riferimenti a quegli oggetti, il loro valore id() viene conservato in un insieme. Gli oggetti dizionario trovati nel ciclo cono i valori di __dict__ per le istanze di Graph, e serbano gli attributi delle loro istanze.

$ python gc_get_referents_cycles.py

Collegamento nodi Graph(uno).next = Graph(due)
Collegamento nodi Graph(due).next = Graph(tre)
Collegamento nodi Graph(tre).next = Graph(uno)

In esame: Graph(tre)
In esame: {'name': 'tre', 'next': Graph(uno)}
In esame: Graph(uno)
In esame: {'name': 'uno', 'next': Graph(due)}
In esame: Graph(due)
In esame: {'name': 'due', 'next': Graph(tre)}

Trovato un ciclo per Graph(tre):
  0: Graph(tre)
  1: {'name': 'tre', 'next': Graph(uno)}
  2: Graph(uno)
  3: {'name': 'uno', 'next': Graph(due)}
  4: Graph(due)

Forzare Garbage Collection

Sebbene il garbage collector venga eseguito automaticamente mentre l'interprete esegue il proprio programma, si potrebbe volerlo attivare ad uno specifico momento, quando si sa che ci sono molti oggetti da liberare oppure che la propria applicazione non sta eseguendo molto lavoro. L'attivazione della raccolta avviene tramite collect().

import gc
import pprint
import Queue

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
    def set_next(self, next):
        print 'Collegamento nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)

# Costruisce un ciclo di Grafi
one = Graph('uno')
two = Graph('due')
three = Graph('tre')
one.set_next(two)
two.set_next(three)
three.set_next(one)

# Rimuove riferimenti ai nodi del grafo nello spazio dei nomi di questo modulo
one = two = three = None

# Mostra gli effetti della garbage collection
for i in range(2):
    print 'In raccolta %d ...' % i
    n = gc.collect()
    print 'Oggetti non raggiungibili:', n
    print 'Garbage rimanente:',
    pprint.pprint(gc.garbage)
    print

In questo esempio, il ciclo viene pulito non appena la raccolta viene eseguita la prima volta, visto che nulla è riferito a nodi Graph tranne essi stessi. collect() ritorna il numero di oggetti "irraggiungibili" che trova. In questo caso, il valore è 6 visto che vi erano tre oggeetti con i propri dizionari con i propri attributi di istanza.

$ python gc_collect.py

Collegamento nodi Graph(uno).next = Graph(due)
Collegamento nodi Graph(due).next = Graph(tre)
Collegamento nodi Graph(tre).next = Graph(uno)
In raccolta 0 ...
Oggetti non raggiungibili: 6
Garbage rimanente:[]

In raccolta 1 ...
Oggetti non raggiungibili: 0
Garbage rimanente:[]

Comunque, se Graph ha un metodo __del__() il garbage collector non può interrompere il ciclo.

import gc
import pprint
import Queue

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
    def set_next(self, next):
        print 'Collegamento nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)
    def __del__(self):
        print '%s.__del__()' % self

# Costruisce un ciclo di Grafi
one = Graph('uno')
two = Graph('due')
three = Graph('tre')
one.set_next(two)
two.set_next(three)
three.set_next(one)

# Rimuove riferimenti ai nodi del grafo nello spazio dei nomi di questo modulo
one = two = three = None

# Mostra gli effetti della garbage collection
print 'In raccolta ...'
n = gc.collect()
print 'Oggetti non raggiungibili:'
print 'Garbage rimanente:',
pprint.pprint(gc.garbage)

Visto che più di un oggetto nel ciclo contiene un metodo di finalizzazione, l'ordine nel quale gli oggetti devono essere finalizzati, quindi raccolti dal garbage collector non si può determinare, quindi il garbage collector agisce in sicurezza e mantiene gli oggetti.

$ python gc_collect_with_del.py

Collegamento nodi Graph(uno).next = Graph(due)
Collegamento nodi Graph(due).next = Graph(tre)
Collegamento nodi Graph(tre).next = Graph(uno)
In raccolta ...
Oggetti non raggiungibili: 6
Garbage rimanente:[Graph(uno), Graph(due), Graph(tre)]

Quando il ciclo viene interrotto, le istanze di Graph possono essere raccolte.

import gc
import pprint
import Queue

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
    def set_next(self, next):
        print 'Collegamento nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)
    def __del__(self):
        print '%s.__del__()' % self

# Costruisce un ciclo di Grafi
one = Graph('uno')
two = Graph('due')
three = Graph('tre')
one.set_next(two)
two.set_next(three)
three.set_next(one)

# Rimuove riferimenti ai nodi del grafo nello spazio dei nomi di questo modulo
one = two = three = None

# Se si raccoglie ora gli oggetti non sono possono essere raccolti
print
print 'In raccolta ...'
n = gc.collect()
print 'Oggetti non raggiungibili:', n
print 'Garbage rimanente:',
pprint.pprint(gc.garbage)

# Si interrompe il ciclo
print
print 'Interruzione del ciclo'
gc.garbage[0].set_next(None)
print 'Riferimenti rimossi in in gc.garbage'
del gc.garbage[:]

# Ora gli oggetti vengono rimoss
print
print 'In raccolta ...'
n = gc.collect()
print 'Oggetti non raggiungibili:'
print 'Garbage rimanente:',
pprint.pprint(gc.garbage)

Visto che gc.garbage mantiene un riferimento agli oggetti dalla precedente esecuzione della raccolta, deve essere pulito dopo la rottura del ciclo affinchè possa diminuire il conteggio dei riferimenti in modo che possano essere finalizzati e liberati.

$ python gc_collect_break_cycle.py

Collegamento nodi Graph(uno).next = Graph(due)
Collegamento nodi Graph(due).next = Graph(tre)
Collegamento nodi Graph(tre).next = Graph(uno)

In raccolta ...
Oggetti non raggiungibili: 6
Garbage rimanente:[Graph(uno), Graph(due), Graph(tre)]

Interruzione del ciclo
Collegamento nodi Graph(uno).next = None
Riferimenti rimossi in in gc.garbage
Graph(due).__del__()
Graph(tre).__del__()
Graph(uno).__del__()

In raccolta ...
Oggetti non raggiungibili:
Garbage rimanente:[]

Trovare Riferimenti ad Oggetti che non Possono Essere Raccolti

La ricerca di oggetti che mantengono un riferimento a qualcosa nel garbage è qualcosa di più complicato rispetto a vedere cosa un oggetto fa riferimento. Visto che il codice che chiede il riferimento deve a sua volta mantenere esso stesso un riferimento, alcuni dei referenti devono essere ignorati. Questo esempio crea un ciclo di grafi, quindi lavora attraverso le istanze di Graph nel garbage e rimuove i riferimenti nel nodo "genitore".

import gc
import pprint
import Queue

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
    def set_next(self, next):
        print 'Collegamento nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)
    def __del__(self):
        print '%s.__del__()' % self

# Costruisce un ciclo di Grafi
one = Graph('uno')
two = Graph('due')
three = Graph('tre')
one.set_next(two)
two.set_next(three)
three.set_next(one)

# Rimuove riferimenti ai nodi del grafo nello spazio dei nomi di questo modulo
one = two = three = None

# Se si raccoglie ora gli oggetti non sono possono essere raccolti
print
print 'In raccolta ...'
n = gc.collect()
print 'Oggetti non raggiungibili:', n
print 'Garbage rimanente:',
pprint.pprint(gc.garbage)

REFERRERS_TO_IGNORE = [ locals(), globals(), gc.garbage ]

def find_referring_graphs(obj):
    print 'Si cercano riferimenti a %s' % repr(obj)
    referrers = (r for r in gc.get_referrers(obj)
                 if r not in REFERRERS_TO_IGNORE)
    for ref in referrers:
        if isinstance(ref, Graph):
            # Un nodo grafo
            yield ref
        elif isinstance(ref, dict):
            # Una istanza od un altro dizionario di spazio dei nomi
            for parent in find_referring_graphs(ref):
                yield parent

# Cerca oggetti che fanno riferimento ad oggetti che rimangono in gc.garbage.
print
print 'Pulizia dei referenti:'
for obj in gc.garbage:
    for ref in find_referring_graphs(obj):
        ref.set_next(None)
        del ref # rimuove riferimento locale così che si possa eliminare il nodo
    del obj # rimuove riferimento locale così che si possa eliminare il nodo

# Pulizia dei riferimenti mantenuti da gc.garbage
print
print 'Pulizia di gc.garbage:'
del gc.garbage[:]

# A questo punto tutto dovrebbe essere stato liberato
print
print 'In raccolta ...'
n = gc.collect()
print 'Oggetti non raggiungibili:', n
print 'Garbage rimanente:',
pprint.pprint(gc.garbage)

Questa sorta di logica è esagerata se in prima battuta si comprende perchè i cicli sono stati creati, tuttavia se si hanno dei cicli non spiegabili nei propri dati, utilizzando get_referrers() si possono esporre le relazioni non previste.

$ python gc_get_referrers.py

Collegamento nodi Graph(uno).next = Graph(due)
Collegamento nodi Graph(due).next = Graph(tre)
Collegamento nodi Graph(tre).next = Graph(uno)

In raccolta ...
Oggetti non raggiungibili: 6
Garbage rimanente:[Graph(uno), Graph(due), Graph(tre)]

Pulizia dei referenti:
Si cercano riferimenti a Graph(uno)
Si cercano riferimenti a {'name': 'tre', 'next': Graph(uno)}
Collegamento nodi Graph(tre).next = None
Si cercano riferimenti a Graph(due)
Si cercano riferimenti a {'name': 'uno', 'next': Graph(due)}
Collegamento nodi Graph(uno).next = None
Si cercano riferimenti a Graph(tre)
Si cercano riferimenti a {'name': 'due', 'next': Graph(tre)}
Collegamento nodi Graph(due).next = None

Pulizia di gc.garbage:
Graph(tre).__del__()
Graph(due).__del__()
Graph(uno).__del__()

In raccolta ...
Oggetti non raggiungibili: 0
Garbage rimanente:[]

Soglie di Raccolta e Generazioni

Il garbage collector mantiene tre liste di oggetti che vede mentre è in esecuzione, una per ogni "generatore" che viene tracciato dal collettore. Mentre gli oggetti vengono esaminati in ogni generazione, sono raccolti oppure passano nelle generazioni successive fino a che raggiungono lo stato nel quale essi sono mantenuti in modo permanente.

Le routine del collettore possono essere regolate per farle eseguire con diversa frequenza in base alle differenze tra li numero di allocazioni e deallocazioni di oggetti tra le esecuzioni. Quando il numero delle allocazioni meno le deallocazioni è maggiore della soglia per la generazione, viene eseguito il garbage collector. La soglia attuale può essere esaminata con get_threshold().

import gc

print gc.get_threshold()

Il valore restituito è una tupla con la soglia per ciascuna generazione.

$ python gc_get_threshold.py

(700, 10, 10)

Le soglie possono essere modificate con set_threshold(). Questo esempio legge le soglie per la generazione 0 dalla riga di comando, quindi modifica le impostazioni di gc ed alloca una serie di oggetti.

import gc
import pprint
import sys

try:
    threshold = int(sys.argv[1])
except (IndexError, ValueError, TypeError):
    print 'Soglia mancante o non valida, si utilizza la predefinita'
    threshold = 5

class MyObj(object):
    def __init__(self, name):
        self.name = name
        print 'Creato', self.name

gc.set_debug(gc.DEBUG_STATS)

gc.set_threshold(threshold, 1, 1)
print 'Soglie:', gc.get_threshold()

print 'Pulisce il collettore forzando una esecuzione'
gc.collect()
print

print 'Creazione oggetti'
objs = []
for i in range(10):
    objs.append(MyObj(i))

Diversi valori di soglia introducone nel garbage collector azioni di raccolta in tempi diversi, qui sotto dimostrato abilitando il debug.

$ python -u gc_threshold.py 5

Soglie: (5, 1, 1)
Pulisce il collettore forzando una esecuzione
gc: collecting generation 2...
gc: objects in each generation: 598 3151 0
gc: done, 0.0005s elapsed.

Creazione oggetti
gc: collecting generation 0...
gc: objects in each generation: 7 0 3473
gc: done, 0.0000s elapsed.
Creato 0
Creato 1
Creato 2
Creato 3
Creato 4
gc: collecting generation 0...
gc: objects in each generation: 6 4 3473
gc: done, 0.0000s elapsed.
Creato 5
Creato 6
Creato 7
Creato 8
Creato 9
gc: collecting generation 2...
gc: objects in each generation: 5 6 3471
gc: done, 0.0004s elapsed.

Una soglia inferiore fa sì che la pulizia venga effettuata con maggiore frequenza

$ python -u gc_threshold.py 2

Soglie: (2, 1, 1)
Pulisce il collettore forzando una esecuzione
gc: collecting generation 2...
gc: objects in each generation: 598 3151 0
gc: done, 0.0004s elapsed.

Creazione oggetti
gc: collecting generation 0...
gc: objects in each generation: 3 0 3473
gc: done, 0.0000s elapsed.
gc: collecting generation 0...
gc: objects in each generation: 4 3 3473
gc: done, 0.0000s elapsed.
Creato 0
Creato 1
gc: collecting generation 1...
gc: objects in each generation: 3 4 3473
gc: done, 0.0000s elapsed.
Creato 2
Creato 3
Creato 4
gc: collecting generation 0...
gc: objects in each generation: 5 0 3478
gc: done, 0.0000s elapsed.
Creato 5
Creato 6
Creato 7
gc: collecting generation 0...
gc: objects in each generation: 5 3 3478
gc: done, 0.0000s elapsed.
Creato 8
Creato 9
gc: collecting generation 2...
gc: objects in each generation: 2 6 3474
gc: done, 0.0004s elapsed.

Debug

Il debug per trovare perdita o fuoriuscita di memoria (memory leak) può essere impegnativo. gc include parecchie opzioni per esporre i meccanismi più interni per facilitare il lavoro. Le opzioni sono dei bit-flag, che si possono combinare e passare a set_debug() per configurare il garbage collector mentre un proprio programma è in esecuzione. Le informazioni di debug sono stampate verso lo stderr

Il flag DEBUG STATS attiva la rilevazione delle statistiche, facendo in modo che il garbage collector rilevi, quando è in esecuzione, il numero di oggetti tracciati per ogni generazione ed il tempo servito per eseguire il passaggio di pulizia.

import gc

gc.set_debug(gc.DEBUG_STATS)

gc.collect()

Il risultato di questo esempio mostra due esecuzioni separate del collettore visto che viene eseguito una volta quando viene chiamato esplicitamente, ed una seconda quando l'interprete esce.

$ python gc_debug_stats.py
gc: collecting generation 2...

gc: objects in each generation: 463 3151 0
gc: done, 0.0009s elapsed.
gc: collecting generation 2...
gc: objects in each generation: 0 0 3401
gc: done, 0.0008s elapsed.

L'attivazione dei flag DEBUG_COLLECTABLE e DEBUG_UNCOLLECTABLE fa sì che il collettore rilevi il fatto che ogni oggetto che esamina possa essere o meno raccolto. Occorre combinare questi flag con DEBUG_OBJECTS in modo che gc stampi le informazioni sugli oggetti trattenuti.

import gc

flags = (gc.DEBUG_COLLECTABLE |
         gc.DEBUG_UNCOLLECTABLE |
         gc.DEBUG_OBJECTS
         )
gc.set_debug(flags)

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
        print 'Creazione di %s 0x%x (%s)' % (self.__class__.__name__, id(self), name)
    def set_next(self, next):
        print 'Connessione nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)

class CleanupGraph(Graph):
    def __del__(self):
        print '%s.__del__()' % self

# Costruisce un ciclo di Grafi
one = Graph('Uno')
two = Graph('Due')
one.set_next(two)
two.set_next(one)

# Costruisce un altro nodo a se stante
three = CleanupGraph('three')

# Costruisce un ciclo di Grafi con un finalizzatore
four = CleanupGraph('four')
five = CleanupGraph('five')
four.set_next(five)
five.set_next(four)

# Elimina i riferimenti ai nodi del grafo in questo spazio dei nomi del modulo
one = two = three = four = five = None

print

# Force una raccolta
print 'In raccolta'
gc.collect()
print 'Fatto'

Le due classi Graph e CleanupGraph sono costruite in modo che sia possibile creare strutture che sono automaticamente raccoglibili dal collettore ed altre dove i cicli devono essere esplicitamente spezzati dall'utente.

Il risultato mostra che le istanze di Graph uno e due creano un ciclo, ma sono comunque raccoglibili visto che non hanno un finalizzatore ed i loro riferimenti in entrata provengono da altri oggetti che possono essere raccolti. Sebbene CleanupGraph abbia un finalizzatore, tre viene raccolto non appena il conteggio dei suoi riferimenti raggiunge lo zero. Al contrario, quattro e cinque creano un ciclo e non possono essere liberati.

$ python -u gc_debug_collectable_objects.py

Creazione di Graph 0x7fcabb293f10 (Uno)
Creazione di Graph 0x7fcabb293f50 (Due)
Connessione nodi Graph(Uno).next = Graph(Due)
Connessione nodi Graph(Due).next = Graph(Uno)
Creazione di CleanupGraph 0x7fcabb293f90 (tre)
Creazione di CleanupGraph 0x7fcabb293fd0 (quattro)
Creazione di CleanupGraph 0x7fcabb2a6050 (cinque)
Connessione nodi CleanupGraph(quattro).next = CleanupGraph(cinque)
Connessione nodi CleanupGraph(cinque).next = CleanupGraph(quattro)
CleanupGraph(tre).__del__()

In raccolta
gc: collectable 
gc: collectable 
gc: collectable 
gc: collectable 
gc: uncollectable 
gc: uncollectable 
gc: uncollectable 
gc: uncollectable 
Fatto

Il flag DEBUG_INSTANCES funziona quansi allo stesso modo per istanze di classi "vecchio-stile" (non derivate da object).

import gc

flags = (gc.DEBUG_COLLECTABLE |
         gc.DEBUG_UNCOLLECTABLE |
         gc.DEBUG_INSTANCES
         )
gc.set_debug(flags)

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
        print 'Creazione di %s 0x%x (%s)' % (self.__class__.__name__, id(self), name)
    def set_next(self, next):
        print 'Connessione nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)

class CleanupGraph(Graph):
    def __del__(self):
        print '%s.__del__()' % self

# Costruisce un ciclo di Grafi
one = Graph('Uno')
two = Graph('Due')
one.set_next(two)
two.set_next(one)

# Costruisce un altro nodo a se stante
three = CleanupGraph('tre')

# Costruisce un ciclo di Grafi con un finalizzatore
four = CleanupGraph('quattro')
five = CleanupGraph('cinque')
four.set_next(five)
five.set_next(four)

# Elimina i riferimenti ai nodi del grafo in questo spazio dei nomi del modulo
one = two = three = four = five = None

print

# Forcza una raccolta
print 'In raccolta'
gc.collect()
print 'Fatto'

In questo caso, comunque, gli oggetti dict che mantengono gli attributi di istanza non sono inclusi nella stampa del risultato

$ python -u gc_debug_collectable_istances.py

Creazione di Graph 0x7fcb3486bed0 (Uno)
Creazione di Graph 0x7fcb3486bf10 (Due)
Connessione nodi Graph(Uno).next = Graph(Due)
Connessione nodi Graph(Due).next = Graph(Uno)
Creazione di CleanupGraph 0x7fcb3486bf50 (tre)
Creazione di CleanupGraph 0x7fcb3486bf90 (quattro)
Creazione di CleanupGraph 0x7fcb3486bfd0 (cinque)
Connessione nodi CleanupGraph(quattro).next = CleanupGraph(cinque)
Connessione nodi CleanupGraph(cinque).next = CleanupGraph(quattro)
CleanupGraph(tre).__del__()

In raccolta
Fatto

Se il poter vedere gli oggetti che non possono essere raccolti non costituisce informazione sufficiente per capire dove i dati sono trattenuti, è possibile abilitare DEBUG_SAVEALL per fare sì che gc preservi tutti gli oggetti che trova senza alcun riferimento nella lista garbage, in modo che possano essere esaminati. Questo aiuta se, ad esempio, non si ha accesso al costruttore per stampare l'id dell'oggetto quando lo stesso viene creato.

import gc

flags = (gc.DEBUG_COLLECTABLE |
         gc.DEBUG_UNCOLLECTABLE |
         gc.DEBUG_OBJECTS |
         gc.DEBUG_SAVEALL
         )

gc.set_debug(flags)

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
        print 'Creazione di %s 0x%x (%s)' % (self.__class__.__name__, id(self), name)
    def set_next(self, next):
        print 'Connessione nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)

class CleanupGraph(Graph):
    def __del__(self):
        print '%s.__del__()' % self

# Costruisce un ciclo di Grafi
one = Graph('Uno')
two = Graph('Due')
one.set_next(two)
two.set_next(one)

# Costruisce un altro nodo a se stante
three = CleanupGraph('tre')

# Costruisce un ciclo di Grafi con un finalizzatore
four = CleanupGraph('quattro')
five = CleanupGraph('cinque')
four.set_next(five)
five.set_next(four)

# Elimina i riferimenti ai nodi del grafo in questo spazio dei nomi del modulo
one = two = three = four = five = None

print

# Forcza una raccolta
print 'In raccolta'
gc.collect()
print 'Fatto'

# Report su quello che è rimasto
for o in gc.garbage:
    if isinstance(o, Graph):
        print 'Trattenuto: %s 0x%x' % (o, id(o))
$ python -u gc_debug_saveall.py

Creazione di Graph 0x7f9cb7580ed0 (Uno)
Creazione di Graph 0x7f9cb7580f10 (Due)
Connessione nodi Graph(Uno).next = Graph(Due)
Connessione nodi Graph(Due).next = Graph(Uno)
Creazione di CleanupGraph 0x7f9cb7580f50 (tre)
Creazione di CleanupGraph 0x7f9cb7580f90 (quattro)
Creazione di CleanupGraph 0x7f9cb7580fd0 (cinque)
Connessione nodi CleanupGraph(quattro).next = CleanupGraph(cinque)
Connessione nodi CleanupGraph(cinque).next = CleanupGraph(quattro)
CleanupGraph(tre).__del__()

In raccolta
gc: collectable 
gc: collectable 
gc: collectable 
gc: collectable 
gc: uncollectable 
gc: uncollectable 
gc: uncollectable 
gc: uncollectable 
Fatto
Trattenuto: Graph(Uno) 0x7f9cb7580ed0
Trattenuto: Graph(Due) 0x7f9cb7580f10
Trattenuto: CleanupGraph(quattro) 0x7f9cb7580f90
Trattenuto: CleanupGraph(cinque) 0x7f9cb7580fd0

Per semplicità, DEBUG_LEAK viene definito come la combinazione di tutte le altre opzioni.

import gc

flags = gc.DEBUG_LEAK

gc.set_debug(flags)

class Graph(object):
    def __init__(self, name):
        self.name = name
        self.next = None
        print 'Creazione di %s 0x%x (%s)' % (self.__class__.__name__, id(self), name)
    def set_next(self, next):
        print 'Connessione nodi %s.next = %s' % (self, next)
        self.next = next
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)

class CleanupGraph(Graph):
    def __del__(self):
        print '%s.__del__()' % self

# Costruisce un ciclo di Grafi
one = Graph('Uno')
two = Graph('Due')
one.set_next(two)
two.set_next(one)

# Costruisce un altro nodo a se stante
three = CleanupGraph('tre')

# Costruisce un ciclo di Grafi con un finalizzatore
four = CleanupGraph('quattro')
five = CleanupGraph('cinque')
four.set_next(five)
five.set_next(four)

# Elimina i riferimenti ai nodi del grafo in questo spazio dei nomi del modulo
one = two = three = four = five = None

print

# Forcza una raccolta
print 'In raccolta'
gc.collect()
print 'Fatto'

# Report su quello che è rimasto
for o in gc.garbage:
    if isinstance(o, Graph):
        print 'Trattenuto: %s 0x%x' % (o, id(o))

Si rammenti che, visto che DEBUG_SAVEALL viene abilitato da DEBUG_LEAK anche gli oggetti non referenziati, che normalmente sarebbero stati raccolti ed eliminati, sono trattenuti.

$ python -u gc_debug_leak.py

Creazione di Graph 0x7f904d552ed0 (Uno)
Creazione di Graph 0x7f904d552f10 (Due)
Connessione nodi Graph(Uno).next = Graph(Due)
Connessione nodi Graph(Due).next = Graph(Uno)
Creazione di CleanupGraph 0x7f904d552f50 (tre)
Creazione di CleanupGraph 0x7f904d552f90 (quattro)
Creazione di CleanupGraph 0x7f904d552fd0 (cinque)
Connessione nodi CleanupGraph(quattro).next = CleanupGraph(cinque)
Connessione nodi CleanupGraph(cinque).next = CleanupGraph(quattro)
CleanupGraph(tre).__del__()

In raccolta
gc: collectable 
gc: collectable 
gc: collectable 
gc: collectable 
gc: uncollectable 
gc: uncollectable 
gc: uncollectable 
gc: uncollectable 
Fatto
Trattenuto: Graph(Uno) 0x7f904d552ed0
Trattenuto: Graph(Due) 0x7f904d552f10
Trattenuto: CleanupGraph(quattro) 0x7f904d552f90
Trattenuto: CleanupGraph(cinque) 0x7f904d552fd0

Vedere anche:

gc
La documentazione della libreria standard per questo modulo.
weakref
Il modulo weakref fornisce riferimenti ad oggetti senza incrementare il loro contatore di riferimenti, in modo che possano essere comunque raccolti dal garbage collector.
Supporting Cyclic Garbage Collection
Materiale di supporto dalla documentazione di Python delle API C.
How does Python manage memory?
Un articolo sulla gestione della memoria di Python di Fredrik Lundh.