2011-02-09 16 views
37

consideri questo scenario:Come clonare un oggetto generatore Python?

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 
import os 

walk = os.walk('/home') 

for root, dirs, files in walk: 
    for pathname in dirs+files: 
     print os.path.join(root, pathname) 

for root, dirs, files in walk: 
    for pathname in dirs+files: 
     print os.path.join(root, pathname)

So che questo esempio è un pò ridondante, ma si dovrebbe considerare che abbiamo bisogno di usare gli stessi dati walk più di una volta. Ho uno scenario di riferimento e l'uso degli stessi dati walk è obbligatorio per ottenere risultati utili.

Ho cercato walk2 = walk per clonare e utilizzare nella seconda iterazione, ma non ha funzionato. La domanda è ... Come posso copiarlo? È mai possibile?

Grazie in anticipo.

+0

Cosa c'è di sbagliato nell'usare 'os.walk ('/ home')' due volte? Come è un problema? –

+2

@ S.Lott Bene, questo tipo di attività varia tanto a ogni esecuzione. Un altro problema è che dopo la prima esecuzione il sistema probabilmente memorizzerà nella cache i risultati, quindi nelle prossime sessioni otterremo risultati non precisi. L'idea è di camminare prima e quindi misurare due scenari passandoli come argomento. :) –

+0

La memorizzazione nella cache non causerà risultati falsi. –

risposta

54

È possibile utilizzare itertools.tee():

walk, walk2 = itertools.tee(walk) 

Si noti che questo potrebbe "avere bisogno di significativo l'extra storage", come sottolinea la documentazione.

+6

inoltre, la [documentazione] (http://docs.python.org/2/library/itertools.html#itertools.tee) dice: "In generale, se un iteratore utilizza la maggior parte o tutti i dati prima che inizi un altro iteratore , è più veloce usare 'list()' invece di 'tee()'. " Dato lo snippet di codice originale dell'OP, scorre una volta completamente, e poi di nuovo, non gli sarebbe consigliabile usare 'list()'? – HorseloverFat

+0

Utilizzare invece un generatore memorizzato nella cache, ad esempio con 'lambda: a_new_generator', come descritto [qui] (http://stackoverflow.com/a/21315536/1959808). –

+1

Vedere anche i commenti a [questa risposta] (http://stackoverflow.com/a/1271481/1959808). –

4

definire una funzione

def walk_home(): 
    for r in os.walk('/home'): 
     yield r 

O anche questo

def walk_home(): 
    return os.walk('/home') 

Entrambi sono utilizzati in questo modo:

for root, dirs, files in walk_home(): 
    for pathname in dirs+files: 
     print os.path.join(root, pathname) 
+1

Anche se non è la risposta alla domanda esatta che l'OP ha chiesto, questo è un buon modo per farlo senza memorizzare l'albero delle directory completo in memoria. +1 –

+3

Il ciclo non è necessario. 'def walk_home(): return os.walk ('/ home')' fa la stessa cosa. – shang

+0

@Sven Marnach: la domanda "esatta" ha poco senso. –

12

Se si sa che si sta per scorrere l'intero generatore per ogni utilizzo, probabilmente otterrai le migliori prestazioni srotolando il generatore in un elenco e utilizzando l'elenco più volte.

walk = list(os.walk('/home'))

1

Questa risposta mira ad estendere/elaborato su ciò che le altre risposte hanno espresso. La soluzione sarà necessariamente diversa a seconda di cosa esattamente si mira a raggiungere.

Se si desidera iterare lo stesso risultato esatto di os.walk più volte, è necessario inizializzare un elenco dal os.walk del iterabili articoli (vale a dire walk = list(os.walk(path))).

Se è necessario garantire i dati rimane la stessa, che è probabilmente l'unica opzione. Tuttavia, ci sono diversi scenari in cui questo non è possibile o desiderabile.

  1. Non sarà possibile list() un iterabile se l'uscita è di dimensioni sufficienti (vale a dire il tentativo di list() un intero file system potrebbe bloccarsi il computer).
  2. Non è consigliabile list() un iterabile se si desidera acquisire dati "freschi" prima di ogni utilizzo.

Nel caso in cui list() non sia adatto, è necessario eseguire il generatore su richiesta. Nota che i generatori sono estinti dopo ogni uso, quindi questo pone un piccolo problema.Per più volte il generatore "rerun", è possibile utilizzare il seguente schema:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 
import os 

class WalkMaker: 
    def __init__(self, path): 
     self.path = path 
    def __iter__(self): 
     for root, dirs, files in os.walk(self.path): 
      for pathname in dirs + files: 
       yield os.path.join(root, pathname) 

walk = WalkMaker('/home') 

for path in walk: 
    pass 

# do something... 

for path in walk: 
    pass 

Il modello di progettazione di cui sopra vi permetterà di mantenere il vostro codice di DRY.