2011-02-13 11 views
54

Come posso rimuovere una linea (o linee) di un asse matplotlib in modo tale che venga effettivamente raccolta dalla garbage collection e rilasci la memoria? Il codice qui sotto sembra cancellare la linea, ma mai rilascia la memoria (anche con le chiamate esplicite per GC.Collect())Come rimuovere le righe in un grafico Matlotlib

from matplotlib import pyplot 
import numpy 
a = numpy.arange(int(1e7)) 
# large so you can easily see the memory footprint on the system monitor. 
fig = pyplot.Figure() 
ax = pyplot.add_subplot(1, 1, 1) 
lines = ax.plot(a) # this uses up an additional 230 Mb of memory. 
# can I get the memory back? 
l = lines[0] 
l.remove() 
del l 
del lines 
# not releasing memory 
ax.cla() # this does release the memory, but also wipes out all other lines. 

è così c'è un modo per cancellare una sola riga da un asse e ottenere la memoria indietro? This potential solution inoltre non funziona.

risposta

40

Sto mostrando che una combinazione di lines.pop(0)l.remove() e del l fa il trucco.

from matplotlib import pyplot 
import numpy, weakref 
a = numpy.arange(int(1e3)) 
fig = pyplot.Figure() 
ax = fig.add_subplot(1, 1, 1) 
lines = ax.plot(a) 

l = lines.pop(0) 
wl = weakref.ref(l) # create a weak reference to see if references still exist 
#      to this object 
print wl # not dead 
l.remove() 
print wl # not dead 
del l 
print wl # dead (remove either of the steps above and this is still live) 

Ho controllato il set di dati di grandi dimensioni e il rilascio della memoria è confermato anche sul monitor di sistema.

Naturalmente il modo più semplice (quando non risoluzione dei problemi) sarebbe quello di pop dalla lista e chiamare remove sull'oggetto linea senza creare un riferimento concreto ad esso:

lines.pop(0).remove() 
+0

Ho eseguito il codice e ho ricevuto: [8:37 pm] @flattop: ~/Desktop/sandbox> python delete_lines.py Sto usando la versione di matplotlib 0.99.1.1 in ubuntu 10.04 –

+1

@David Morton Ho appena effettuato il downgrade a 0.99.1 e ora riproduco il problema. Credo di poter solo raccomandare l'aggiornamento a 1.0.1.C'era un * lotto * di bugfix dal 0.99.x – Paul

+0

Grazie! Apprezzo molto il tuo aiuto. –

2

(utilizzando lo stesso esempio come il ragazzo sopra)

from matplotlib import pyplot 
import numpy 
a = numpy.arange(int(1e3)) 
fig = pyplot.Figure() 
ax = fig.add_subplot(1, 1, 1) 
lines = ax.plot(a) 

for i, line in enumerate(ax.lines): 
    ax.lines.pop(i) 
    line.remove() 
9

Ho provato molte risposte diverse in diversi forum. Immagino dipenda dalla macchina che stai sviluppando. Ma ho usato la dichiarazione

ax.lines = [] 

e funziona perfettamente. Non uso cla() perché elimina tutte le definizioni che ho apportato alla trama

Es.

pylab.setp(_self.ax.get_yticklabels(), fontsize=8) 

ma ho provato a eliminare le righe molte volte. Anche usando la libreria weakref per verificare il riferimento a quella linea mentre stavo cancellando, ma niente ha funzionato per me.

Spero che questo funziona per qualcun altro = D

+0

Il problema qui è probabilmente un problema di riferimenti in giro quando non dovrebbero essere. Scommetterei che l'OP stava usando IPython per testare le cose. Vedi la mia risposta. – Vorticity

46

Questo è un tempo molto lungo spiegazione che ho scritto per un collega di miniera. Penso che sarebbe utile anche qui. Sii paziente, però. Arrivo al vero problema che stai avendo verso la fine. Proprio come un teaser, è un problema di avere riferimenti extra ai tuoi oggetti Line2D in giro.

ATTENZIONE: Un'altra nota prima di immergerci. Se si sta utilizzando IPython per verificarlo, IPython mantiene i propri riferimenti e non tutti sono weakrefs. Quindi, testare la garbage collection in IPython non funziona. Semplicemente confonde le cose.

Ok, ci siamo. Ogni oggetto matplotlib (Figure, Axes, ecc.) Fornisce l'accesso ai suoi artisti figlio tramite vari attributi. L'esempio seguente sta diventando piuttosto lungo, ma dovrebbe essere illuminante.

Iniziamo creando un oggetto Figure, quindi aggiungere un oggetto Axes a tale figura. Notare che ax e fig.axes[0] sono lo stesso oggetto (stesso id()).

>>> #Create a figure 
>>> fig = plt.figure() 
>>> fig.axes 
[] 

>>> #Add an axes object 
>>> ax = fig.add_subplot(1,1,1) 

>>> #The object in ax is the same as the object in fig.axes[0], which is 
>>> # a list of axes objects attached to fig 
>>> print ax 
Axes(0.125,0.1;0.775x0.8) 
>>> print fig.axes[0] 
Axes(0.125,0.1;0.775x0.8) #Same as "print ax" 
>>> id(ax), id(fig.axes[0]) 
(212603664, 212603664) #Same ids => same objects 

Questo si estende anche alle linee in un asse oggetto:

>>> #Add a line to ax 
>>> lines = ax.plot(np.arange(1000)) 

>>> #Lines and ax.lines contain the same line2D instances 
>>> print lines 
[<matplotlib.lines.Line2D object at 0xce84bd0>] 
>>> print ax.lines 
[<matplotlib.lines.Line2D object at 0xce84bd0>] 

>>> print lines[0] 
Line2D(_line0) 
>>> print ax.lines[0] 
Line2D(_line0) 

>>> #Same ID => same object 
>>> id(lines[0]), id(ax.lines[0]) 
(216550352, 216550352) 

Se si dovesse chiamare plt.show() con quanto fatto in precedenza, si vede una figura contenente una serie di assi e una singola linea :

A figure containing a set of axes and a single line

Ora, mentre abbiamo visto che i contenuti di lines e ax.lines è lo stesso, è molto importante notare che l'oggetto si riferisce la variabile lines non è la stessa come l'oggetto riverito da ax.lines come si può vedere dalla seguente:

>>> id(lines), id(ax.lines) 
(212754584, 211335288) 

Di conseguenza, la rimozione di un elemento da lines non esegue nulla per il grafico corrente, ma la rimozione di un elemento da ax.lines rimuove quella linea dal grafico corrente. Quindi:

>>> #THIS DOES NOTHING: 
>>> lines.pop(0) 

>>> #THIS REMOVES THE FIRST LINE: 
>>> ax.lines.pop(0) 

Quindi, se si dovesse eseguire la seconda riga di codice, è necessario rimuovere l'oggetto Line2D contenuta nel ax.lines[0] dalla trama attuale e sarebbe andato. Si noti che questo può essere fatto anche tramite ax.lines.remove() senso che è possibile salvare un'istanza Line2D in una variabile, poi passarlo a ax.lines.remove() cancellare quella linea, in questo modo:

>>> #Create a new line 
>>> lines.append(ax.plot(np.arange(1000)/2.0)) 
>>> ax.lines 
[<matplotlib.lines.Line2D object at 0xce84bd0>, <matplotlib.lines.Line2D object at 0xce84dx3>] 

A figure containing a set of axes and two lines

>>> #Remove that new line 
>>> ax.lines.remove(lines[0]) 
>>> ax.lines 
[<matplotlib.lines.Line2D object at 0xce84dx3>] 

A figure containing a set of axes and only the second line

Tutti i suddetti articoli per fig.axes funzionano altrettanto bene per ax.lines

Ora, il vero problema qui. Se conserviamo il riferimento contenuto in ax.lines[0] in un oggetto weakref.ref, quindi tentare di eliminarlo, noteremo che non ottiene garbage collection:

>>> #Create weak reference to Line2D object 
>>> from weakref import ref 
>>> wr = ref(ax.lines[0]) 
>>> print wr 
<weakref at 0xb758af8; to 'Line2D' at 0xb757fd0> 
>>> print wr() 
<matplotlib.lines.Line2D at 0xb757fd0> 

>>> #Delete the line from the axes 
>>> ax.lines.remove(wr()) 
>>> ax.lines 
[] 

>>> #Test weakref again 
>>> print wr 
<weakref at 0xb758af8; to 'Line2D' at 0xb757fd0> 
>>> print wr() 
<matplotlib.lines.Line2D at 0xb757fd0> 

Il riferimento è ancora vivo! Perché? Questo perché esiste ancora un altro riferimento all'oggetto Line2D a cui punta il riferimento in wr. Ricorda come lo lines non aveva lo stesso ID di ax.lines ma conteneva gli stessi elementi? Bene, questo è il problema.

>>> #Print out lines 
>>> print lines 
[<matplotlib.lines.Line2D object at 0xce84bd0>, <matplotlib.lines.Line2D object at 0xce84dx3>] 

To fix this problem, we simply need to delete `lines`, empty it, or let it go out of scope. 

>>> #Reinitialize lines to empty list 
>>> lines = [] 
>>> print lines 
[] 
>>> print wr 
<weakref at 0xb758af8; dead> 

Quindi, la morale della storia è, ripulisci te stesso. Se ti aspetti che qualcosa sia raccolto dalla spazzatura ma non lo è, probabilmente lascerai un riferimento da qualche parte.

+2

Esattamente quello di cui avevo bisogno. Sto tracciando migliaia di mappe, ognuna con una trama sparsa in cima a una proiezione della mappa del mondo. Stavano prendendo 3 secondi ciascuno! Riutilizzando la figura con la mappa già disegnata e facendo scoppiare la raccolta risultante da ax.collections, l'ho ridotta a 1/3 di secondo. Grazie! – GaryBishop

+0

Felice di poterti aiutare! – Vorticity

+3

Penso che questo non sia più necessario nelle attuali versioni di mpl. L'artista ha una funzione 'remove()' che la ripulirà dal lato mpl delle cose, e quindi dovrai solo tenere traccia dei tuoi riferimenti. – tacaswell

Problemi correlati