gc - Garbage Collector

Scopo: Gestisce la memoria usata dagli oggetti Python

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 è possibile liberare.

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 sa che la struttura dati ha un ciclo, è possibile costruire del codice personalizzato per esaminarne le proprietà. Se il ciclo è in codice sconosciuto le funzioni get_referents() e get_referrers possono essere usate per costruire strumenti generici di debug.

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

# gc_get_referents.py

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 {}.next = {}'.format(self, next))
        self.next = next

    def __repr__(self):
        return '{}({})'.format(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'istanza three di Graph mantiene un riferimento al suo dizionario di istanze (nell'attributo __dict__) ed alla sua classe.

$ python3 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)}
<class '__main__.Graph'>
<class '__main__.Graph'>

Il prossimo esempio utilizza una Queue per eseguire una ricerca in ampiezza di tutti i riferimenti di oggetto per trovare dei cicli. Gli elementi inseriti nella coda 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 evita il cercare tra metodi, moduli, ecc.

# gc_get_referents_cycles.py

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 {}.next = {}'.format(self, next))
        self.next = next

    def __repr__(self):
        return '{}({})'.format(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 tre.
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, str) or isinstance(r, type):
            # Ignorastringhe e classi
            pass
        elif id(r) in seen:
            print
            print('Trovato un ciclo per {}:'.format(r))
            for i, link in enumerate(chain):
                print('  {}: '.format(i), end=' ')
                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.

$ python3 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: {'next': Graph(uno), 'name': 'tre'}
In esame: Graph(uno)
In esame: {'next': Graph(due), 'name': 'uno'}
In esame: Graph(due)
In esame: {'next': Graph(tre), 'name': 'due'}
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)
  5:  {'name': 'due', 'next': Graph(tre)}

Forzare Garbage Collection

Sebbene il garbage collector venga eseguito automaticamente mentre l'interprete esegue un programma, si potrebbe volerlo attivare ad uno specifico momento, quando si sa che ci sono molti oggetti da liberare oppure non c'è troppa attività e la raccolta con il collettore non danneggia le prestazioni dell'applicazione. L'attivazione della raccolta avviene tramite collect().

# gc_collect.py

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 {}.next = {}'.format(self, next))
        self.next = next

    def __repr__(self):
        return '{}({})'.format(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 del garbage collection
for i in range(2):
    print('In raccolta {} ...'.format(i))
    n = gc.collect()
    print('Oggetti non raggiungibili:', n)
    print('Garbage rimanente:', end=' ')
    pprint.pprint(gc.garbage)

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 oggetti con i propri dizionari con gli attributi di istanza.

$ python3 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: []

Trovare Riferimenti ad Oggetti che Non Possono Essere Raccolti

La ricerca di oggetti che mantengono un riferimento a qualcosa nella lista garbage è qualcosa di più complicato rispetto a vedere a cosa un oggetto fa riferimento. Visto che il codice che chiede il riferimento deve a sua volta mantenere 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".

# gc_get_referrers.py

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 {}.next = {}'.format(self, next))
        self.next = next

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, self.name)

    def __del__(self):
        print('{}.__del__()'.format(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:', end=' ')
pprint.pprint(gc.garbage)

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


def find_referring_graphs(obj):
    print('Si cercano riferimenti a {}').format(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 per poter 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:')
print('Garbage rimanente:', end=' ')
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 dati, utilizzando get_referrers() si possono esporre le relazioni non attese.

$ python3 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 ...
Graph(uno).__del__()
Graph(due).__del__()
Graph(tre).__del__()
Oggetti non raggiungibili: 6
Garbage rimanente: []

Pulizia dei referenti:

Pulizia di gc.garbage:

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

Soglie di Raccolta e Generazioni

Il garbage collector mantiene tre liste di oggetti che vede mentre è in esecuzione, una per ogni "generazione" che viene tracciata dal collettore. Mentre gli oggetti vengono esaminati in ogni generazione, essi 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().

# gc_get_treshold

import gc

print(gc.get_threshold())

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

$ python3 gc_get_threshold.py

(700, 10, 10)

Le soglie possono essere modificate con set_threshold(). Questo esempio utilizza un argomento da riga di comando per impostare la soglia per la generazione a 0, quindi alloca una serie di oggetti.

# gc_threshold.py

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))
print('Uscita')

# Chiude il debug
gc.set_debug(0)

Diversi valori di soglia introducono azioni di raccolta in tempi diversi, qui sotto dimostrato abilitando il debug.

$ python3 -u gc_threshold.py 5

Soglie: (5, 1, 1)
Pulisce il collettore forzando una esecuzione
gc: collecting generation 2...
gc: objects in each generation: 507 1201 4814
gc: done, 0.0014s elapsed

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

Una soglia inferiore fa sì che l'azione di raccolta venga effettuata con maggiore frequenza.

$ python3 -u gc_threshold.py 2
Soglie: (2, 1, 1)
Pulisce il collettore forzando una esecuzione
gc: collecting generation 2...
gc: objects in each generation: 507 1201 4814
gc: done, 0.0012s elapsed
gc: collecting generation 0...
gc: objects in each generation: 3 0 6349
gc: done, 0.0000s elapsed

Creazione oggetti
gc: collecting generation 0...
gc: objects in each generation: 6 0 6349
gc: done, 0.0000s elapsed
gc: collecting generation 1...
gc: objects in each generation: 3 4 6349
gc: done, 0.0000s elapsed
Creato 0
Creato 1
gc: collecting generation 0...
gc: objects in each generation: 4 0 6351
gc: done, 0.0000s elapsed
Creato 2
gc: collecting generation 0...
gc: objects in each generation: 8 1 6351
gc: done, 0.0000s elapsed
Creato 3
Creato 4
gc: collecting generation 1...
gc: objects in each generation: 4 3 6351
gc: done, 0.0000s elapsed
Creato 5
gc: collecting generation 0...
gc: objects in each generation: 8 0 6355
gc: done, 0.0000s elapsed
Creato 6
Creato 7
gc: collecting generation 0...
gc: objects in each generation: 4 2 6355
gc: done, 0.0000s elapsed
Creato 8
gc: collecting generation 1...
gc: objects in each generation: 8 3 6355
gc: done, 0.0000s elapsed
Creato 9
Uscita

Debug

Il debug per trovare 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, concepiti per essere combinati e passati a set_debug() per configurare il garbage collector mentre il programma è in esecuzione. Le informazioni di debug sono stampate verso sys.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.

# gc_debug_stats.py

import gc

gc.set_debug(gc.DEBUG_STATS)

gc.collect()
print('Uscita')

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.

$ python3 gc_debug_stats.py

gc: collecting generation 2...
gc: objects in each generation: 554 4417 0
gc: done, 0.0010s elapsed
Uscita
gc: collecting generation 2...
gc: objects in each generation: 1 0 4800
gc: done, 0.0009s elapsed
gc: collecting generation 2...
gc: objects in each generation: 79 0 4637
gc: done, 246 unreachable, 0 uncollectable, 0.0010s elapsed
gc: collecting generation 2...
gc: objects in each generation: 0 0 3931
gc: done, 1139 unreachable, 0 uncollectable, 0.0010s 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.

# gc_debug_collectable_objects.py

import gc

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


class Graph:

    def __init__(self, name):
        self.name = name
        self.next = None
        print('Creating {} 0x{:x} ({})'.format(
            self.__class__.__name__, id(self), name))

    def set_next(self, next):
        print('Collegamento nodi {}.next = {}'.format(self, next))
        self.next = next

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, self.name)


class CleanupGraph(Graph):

    def __del__(self):
        print('{}.__del__()'.format(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()

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

# Reimposta i flag di debug prima di uscire per evitare di scaricare molte
# nformazioni extra che renderebbe l'output di esempio meno chiaro
gc.set_debug(0)

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 interrotti dall'utente.

Il risultato mostra che le istanze di Graph one e two 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, three viene raccolto non appena il conteggio dei suoi riferimenti raggiunge lo zero. Al contrario, four e five creano un ciclo e non possono essere liberati.

$ python3 gc_debug_collectable_objects.py

Creating Graph 0x7f983a20ec18 (uno)
Creating Graph 0x7f983a20ee48 (due)
Collegamento nodi Graph(uno).next = Graph(due)
Collegamento nodi Graph(due).next = Graph(uno)
Creating CleanupGraph 0x7f983a20ee80 (tre)
Creating CleanupGraph 0x7f983a20eeb8 (quattro)
Creating CleanupGraph 0x7f983a20eef0 (cinque)
Collegamento nodi CleanupGraph(quattro).next = CleanupGraph(cinque)
Collegamento nodi CleanupGraph(cinque).next = CleanupGraph(quattro)
CleanupGraph(tre).__del__()

In raccolta
gc: collectable <Graph 0x7f983a20ec18>
gc: collectable <Graph 0x7f983a20ee48>
gc: collectable <dict 0x7f983a292b48>
gc: collectable <dict 0x7f983a292bc8>
gc: collectable <CleanupGraph 0x7f983a20eeb8>
gc: collectable <CleanupGraph 0x7f983a20eef0>
gc: collectable <dict 0x7f983a212248>
gc: collectable <dict 0x7f983a209f48>
CleanupGraph(quattro).__del__()
CleanupGraph(cinque).__del__()
Fatto

Se visualizzando gli oggetti che non possono essere raccolti non si ottengono sufficienti informazioni circa i dati che vengono trattenuti, abilitando Il flag DEBUG_SAVEALL consente a gc di preservare tutti gli oggetti che trova senza riferimenti nella lista degli elementi raccoglibili.

# gc_debug_saveall.py

import gc

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

gc.set_debug(flags)


class Graph(object):

    def __init__(self, name):
        self.name = name
        self.next = None

    def set_next(self, next):
        self.next = next

    def __repr__(self):
        return '{}({})'.format(
            self.__class__.__name__, self.name)


class CleanupGraph(Graph):

    def __del__(self):
        print('{}.__del__()'.format(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)

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

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

# Riporta ciò che è rimasto
for o in gc.garbage:
    if isinstance(o, Graph):
        print('Trattenuto: {} 0x{:x}'.format(o, id(o)))

# Reimposta i flag di debug prima di uscire per evitare di scaricare molte
# nformazioni extra che renderebbe l'output di esempio meno chiaro
gc.set_debug(0)

Questo fa sì che gli oggetti possano essere esaminati dopo la raccolta, il che è utile se, ad esempio, il costruttore non può essere modificato per stampare l'identificativo dell'oggetto quando ciascun oggetto viene creato.

$ python3 -u  gc_debug_saveall.py

CleanupGraph(tre).__del__()
In raccolta
gc: collectable <Graph 0x7f5adae3ed30>
gc: collectable <Graph 0x7f5adae3eda0>
gc: collectable <dict 0x7f5adaec2b48>
gc: collectable <dict 0x7f5adaec2bc8>
gc: collectable <CleanupGraph 0x7f5adae3ef28>
gc: collectable <CleanupGraph 0x7f5adae3ef60>
gc: collectable <dict 0x7f5adae42248>
gc: collectable <dict 0x7f5adae39f48>
CleanupGraph(quattro).__del__()
CleanupGraph(cinque).__del__()
Fatto
Trattenuto: Graph(uno) 0x7f5adae3ed30
Trattenuto: Graph(due) 0x7f5adae3eda0
Trattenuto: CleanupGraph(quattro) 0x7f5adae3ef28
Trattenuto: CleanupGraph(cinque) 0x7f5adae3ef60

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

# gc_debug_leak.py

import gc

flags = gc.DEBUG_LEAK

gc.set_debug(flags)


class Graph:

    def __init__(self, name):
        self.name = name
        self.next = None

    def set_next(self, next):
        self.next = next

    def __repr__(self):
        return '{}({})'.format(
            self.__class__.__name__, self.name)


class CleanupGraph(Graph):

    def __del__(self):
        print('{}.__del__()'.format(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

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

# Riporta ciò che è rimasto
for o in gc.garbage:
    if isinstance(o, Graph):
        print('Trattenuto: {} 0x{:x}'.format(o, id(o)))

# Reimposta i flag di debug prima di uscire per evitare di scaricare molte
# nformazioni extra che renderebbe l'output di esempio meno chiaro
gc.set_debug(0)

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.

$ python3 -u  gc_debug_leak.py

CleanupGraph(tre).__del__()
In raccolta
gc: collectable <Graph 0x7fcef2758d30>
gc: collectable <Graph 0x7fcef2758da0>
gc: collectable <dict 0x7fcef27dcb48>
gc: collectable <dict 0x7fcef27dcbc8>
gc: collectable <CleanupGraph 0x7fcef2758f28>
gc: collectable <CleanupGraph 0x7fcef2758f60>
gc: collectable <dict 0x7fcef275c248>
gc: collectable <dict 0x7fcef2753f48>
CleanupGraph(quattro).__del__()
CleanupGraph(cinque).__del__()
Fatto
Trattenuto: Graph(Uno) 0x7fcef2758d30

Trattenuto: Graph(Due) 0x7fcef2758da0
Trattenuto: CleanupGraph(quattro) 0x7fcef2758f28
Trattenuto: CleanupGraph(cinque) 0x7fcef2758f60

Vedere anche:

gc
La documentazione della libreria standard per questo modulo.
weakref
Il modulo weakref fornisce un modo per creare 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.