2012-03-17 8 views
7

... e ogni ciclo for sembrava una di lista.mi sono trovato a oscillare il martello di lista

Invece di:

for stuff in all_stuff: 
    do(stuff) 

stavo facendo (non assegnare la lista per nulla):

[ do(stuff) for stuff in all_stuff ] 

Si tratta di un modello comune trovato su list-comp how-to's. 1) OK, quindi non è un problema? Sbagliato. 2) Non può essere solo uno stile di codice? Super sbagliato.

1) Sì che era sbagliato. Come sottolinea NiklasB, l'HowTos è quello di creare una nuova lista.

2) Forse, ma la sua non è evidente ed esplicito, quindi meglio non usarlo.

non ho tenuto a mente che questi how-to erano in gran parte della riga di comando basata. Dopo che la mia squadra ha urlato contro di me chiedendomi perché diavolo stavo costruendo liste massicce e poi lasciandole andare, mi è venuto in mente che potrei introdurre un grosso bug relativo alla memoria.

Così here'er mia domanda/s. Se dovessi farlo in un processo di lunga durata, in cui venivano consumati molti dati, questa "lista" continuerebbe a consumare la mia memoria fino a quando non verrà rilasciata? Quando il garbage collector riporterà indietro la memoria? Dopo lo scopo, questo elenco è integrato è perso?

La mia ipotesi è sì, non mancherà di tenere consumando la mia memoria. Non so come funzioni il garbage collector di Python, ma mi azzarderei a dire che questo elenco esisterà fino a quando l'ultimo next viene chiamato su all_stuff.

MODIFICA.

L'essenza della mia domanda è diffusa molto più pulito in this question (grazie per il link Niklas)

risposta

6

Se dovessi fare questo in un processo molto lungo in esecuzione, in cui si consumava grandi quantità di dati, sarebbe questa "lista" solo continuare a consumare la mia memoria fino a lasciarsi andare ?

Assolutamente.

Quando il netturbino reclamerà la memoria? Dopo lo scopo, questo elenco è integrato è perso?

CPython utilizza il conteggio dei riferimenti, quindi è il caso più probabile. Altre implementazioni funzionano in modo diverso, quindi non contate su di esso.

Grazie a Karl per aver sottolineato che a causa dei complessi meccanismi di gestione della memoria utilizzati da CPython, questo non significa che la memoria viene immediatamente restituita al sistema operativo successivamente.

Non so come funzioni il garbage collector di Python, ma mi azzarderei a dire che questa lista esisterà fino a dopo l'ultimo dopo viene chiamato su all_stuff.

Non credo alcun garbage collector funziona così. Di solito fanno mark-and-sweep, quindi potrebbe passare un po 'di tempo prima che la lista venga raccolta.

Questo è un modello comune trovato su istruzioni di lista-comp.

Assolutamente no. Il punto è che si itera la lista con lo scopo di fare qualcosa con ogni oggetto (do è chiamato per il suo side-effects). In tutti gli esempi del List-comp HOWTO, l'elenco viene iterato su e crea un nuovo elenco in base agli elementi di quello vecchio. Diamo un'occhiata a un esempio:

# list comp, creates the list [0,1,2,3,4,5,6,7,8,9] 
[i for i in range(10)] 

# loop, does nothing 
for i in range(10): 
    i # meh, just an expression which doesn't have an effect 

Forse sarete d'accordo che questo ciclo è assolutamente senza senso, in quanto non fa nulla, in contrasto con la comprensione, che costruisce una lista. Nel tuo esempio, è il contrario: la comprensione è completamente priva di senso, perché non hai bisogno della lista! È possibile trovare ulteriori informazioni sul problema su un related question

A proposito, se si vuole veramente scrivere quel loop su una riga, utilizzare un utente generatore come deque.extend. Questo sarà leggermente più lento di un for anello crudo a questo semplice esempio, se:

>>> from collections import deque 
>>> consume = deque(maxlen=0).extend 
>>> consume(do(stuff) for stuff in all_stuff) 
+0

Si può fare alcuni benchmark 'timeit' per il vostro ultimo codice bloccare? – Blender

+0

@Blender: Meh, non riesco a dimostrarlo ... Grazie per avermi costretto a impararlo nel modo più duro: P –

+0

C'era una domanda qualche tempo fa a riguardo: [Passando agli iteratori per l'esecuzione per velocità e perché?] (http://stackoverflow.com/q/9144934/1132524) –

3

Prova a fare manualmente GC e il dumping delle statistiche.

gc.DEBUG_STATS

statistiche di stampa durante la raccolta. Questa informazione può essere utile quando si sintonizza la frequenza di raccolta.

DA

http://docs.python.org/library/gc.html

2

Il CPython GC raccoglierà una volta non ci sono riferimenti ad esso al di fuori di un ciclo. Jython e IronPython seguono le regole dei GC sottostanti.

0

Non so come funzioni il garbage collector di Python, ma mi azzarderei a dire che questa lista esisterà fino a dopo l'ultimo dopo viene chiamato su all_stuff.

Beh, certo che lo sarà, dal momento che stai creando un elenco che avrà lo stesso numero di elementi di all_stuff. L'interprete non può scartare la lista prima che sia finita, vero? Potresti chiamare gc.collect tra uno di questi loop e un altro, ma ognuno sarà completamente costruito prima di poter essere recuperato.

In alcuni casi è possibile utilizzare un generatore di espressione invece di una lista di comprensione, in modo che non ha bisogno di costruire una lista con tutti i tuoi valori:

(do_something(i) for i in xrange(1000)) 

Tuttavia saresti ancora deve "exaust "quel generatore in qualche modo ...

+0

Questo era il problema, all_stuff era un generatore che genera dati di rete. Non si sarebbe esaurito presto. – sbartell

+0

Intendevo dire che dovevi assicurarti che l'interprete eseguisse l'iterazione sul generatore (scusate il mio inglese). Usando uno dei suggerimenti di altre persone, come 'any' o' deque.extend' consumerebbe ogni elemento non appena vengono generati, senza memorizzarli in un elenco. – mgibsonbr

2

Se ti piace quel linguaggio, do restituisce qualcosa che sempre viene valutato come True o False e sarebbe prendere in considerazione una simile alternativa senza effetti collaterali brutto, è possibile utilizzare un generatore di espressione combinato con any o all.

Per le funzioni che restituiscono valori false (o non ritorno):

any(do(stuff) for stuff in all_stuff) 

Per le funzioni che restituiscono valori veri:

all(do(stuff) for stuff in all_stuff) 
+2

A meno che 'do' abbia un valore di ritorno significativo che semplicemente non viene guardato qui. 'any' esaurisce solo l'iteratore fino al primo valore True che produce. – lvc

+0

Così vero. Mio male, grazie per averlo indicato. –

+0

Si noti che a volte ci sono vantaggi prestazionali (almeno con CPython) con questo metodo rispetto ad un ciclo normale. – agf

Problemi correlati