2011-10-30 14 views
17

Sono consapevole che posso aprire più file con qualcosa di simile,di apertura (un numero non specificato) dei file in una sola volta e assicurando che siano correttamente chiusi

with open('a', 'rb') as a, open('b', 'rb') as b: 

Ma io ho una situazione in cui ho una lista di file da aprire e mi chiedo quale sia il metodo preferito per fare lo stesso quando il numero di file è sconosciuto in anticipo. Qualcosa di simile,

with [ open(f, 'rb') for f in files ] as fs: 

(ma questo non riesce con un AttributeError poiché elenco non implementa __exit__)

Non mi dispiace usare qualcosa di simile,

try: 
    fs = [ open(f, 'rb') for f in files ] 

    .... 

finally: 
    for f in fs: 
     f.close() 

ma non sono sicuro che cosa accadrà se alcuni file vengono lanciati quando si tenta di aprirli. fs sarà definito correttamente, con i file che è riuscito ad aprire, nel blocco finally?

+0

si sarà accedere a questi file in parallelo o in sequenza? –

+0

@EthanFurman In parallelo. – tjm

risposta

12

No, il codice non inizializza fs a meno che tutte le chiamate open() siano state completate correttamente. Questo dovrebbe funzionare anche se:

fs = [] 
try: 
    for f in files: 
     fs.append(open(f, 'rb')) 

    .... 

finally: 
    for f in fs: 
     f.close() 

Si noti inoltre che f.close() potrebbe non riuscire quindi si consiglia di prendere e ignorare (o in altro modo gestire) eventuali guasti lì.

1

Gli errori possono verificarsi quando si tenta di aprire un file, quando si tenta di leggere da un file e (molto raramente) quando si tenta di chiudere un file.

Quindi un errore di struttura di gestione di base potrebbe essere simile:

try: 
    stream = open(path) 
    try: 
     data = stream.read() 
    finally: 
     stream.close() 
except EnvironmentError as exception: 
    print 'ERROR:', str(exception) 
else: 
    print 'SUCCESS' 
    # process data 

Questo assicura che close sarà sempre chiamato se esiste la variabile stream. Se stream non esiste, allora open deve aver avuto esito negativo e quindi non vi è alcun file da chiudere (nel qual caso, il blocco eccetto verrà eseguito immediatamente).

Avete davvero bisogno di avere i file aperti in parallelo, o possono essere elaborati in sequenza? Se quest'ultimo, quindi qualcosa come il codice di elaborazione file sopra dovrebbe essere messo in una funzione, che viene poi chiamato per ogni percorso nella lista.

7

Certo, perché no? Ecco una ricetta che dovrebbe farlo. Creare un "pool" di gestione dei contesti che può inserire un numero arbitrario di contesti (chiamando il metodo enter()) e verranno ripuliti alla fine della suite.

class ContextPool(object): 
    def __init__(self): 
     self._pool = [] 

    def __enter__(self): 
     return self 

    def __exit__(self, exc_type, exc_value, exc_tb): 
     for close in reversed(self._pool): 
      close(exc_type, exc_value, exc_tb) 

    def enter(self, context): 
     close = context.__exit__ 
     result = context.__enter__() 
     self._pool.append(close) 
     return result 

Ad esempio:

>>> class StubContextManager(object): 
...  def __init__(self, name): 
...   self.__name = name 
...  def __repr__(self): 
...   return "%s(%r)" % (type(self).__name__, self.__name) 
... 
...  def __enter__(self): 
...   print "called %r.__enter__()" % (self) 
... 
...  def __exit__(self, *args): 
...   print "called %r.__exit__%r" % (self, args) 
... 
>>> with ContextPool() as pool: 
...  pool.enter(StubContextManager("foo")) 
...  pool.enter(StubContextManager("bar")) 
...  1/0 
... 
called StubContextManager('foo').__enter__() 
called StubContextManager('bar').__enter__() 
called StubContextManager('bar').__exit__(<type 'exceptions.ZeroDivisionError'>, ZeroDivisionError('integer division or modulo by zero',), <traceback object at 0x02958648>) 
called StubContextManager('foo').__exit__(<type 'exceptions.ZeroDivisionError'>, ZeroDivisionError('integer division or modulo by zero',), <traceback object at 0x02958648>) 

Traceback (most recent call last): 
    File "<pyshell#67>", line 4, in <module> 
    1/0 
ZeroDivisionError: integer division or modulo by zero 
>>> 

Avvertenze: i manager di contesto non sono tenuti a sollevare eccezioni nei loro __exit__() metodi, ma se lo fanno, questa ricetta non fa la pulizia per tutto il contesto manager. Allo stesso modo, anche se ogni gestore di contesto indica che un'eccezione dovrebbe essere ignorata (restituendo True dai loro metodi di uscita), ciò consentirà comunque di sollevare l'eccezione.

1

Grazie per tutte le vostre risposte. Prendendo ispirazione da tutti voi, ho trovato il seguente. Penso (spero) funzioni come volevo.Non ero sicuro se pubblicarlo come una risposta o un'aggiunta alla domanda, ma pensavo che una risposta fosse più appropriata di allora se non riesce a fare ciò che ho chiesto può essere commentato in modo appropriato.

Può essere utilizzato ad esempio come questo ..

with contextlist([open, f, 'rb'] for f in files) as fs: 
    .... 

o come questo ..

f_lock = threading.Lock() 
with contextlist(f_lock, ([open, f, 'rb'] for f in files)) as (lock, *fs): 
    .... 

E qui è,

import inspect 
import collections 
import traceback 

class contextlist: 

    def __init__(self, *contexts): 

     self._args = [] 

     for ctx in contexts: 
      if inspect.isgenerator(ctx): 
       self._args += ctx 
      else: 
       self._args.append(ctx) 


    def __enter__(self): 

     if hasattr(self, '_ctx'): 
      raise RuntimeError("cannot reenter contextlist") 

     s_ctx = self._ctx = [] 

     try: 
      for ctx in self._args: 

       if isinstance(ctx, collections.Sequence): 
        ctx = ctx[0](*ctx[1:]) 

       s_ctx.append(ctx) 

       try: 
        ctx.__enter__() 
       except Exception: 
        s_ctx.pop() 
        raise 

      return s_ctx 

     except: 
      self.__exit__() 
      raise 


    def __exit__(self, *exc_info): 

     if not hasattr(self, '_ctx'): 
      raise RuntimeError("cannot exit from unentered contextlist") 

     e = [] 

     for ctx in reversed(self._ctx): 
      try: 
       ctx.__exit__() 
      except Exception: 
       e.append(traceback.format_exc()) 

     del self._ctx 

     if not e == []: 
      raise Exception('\n> '*2+(''.join(e)).replace('\n','\n> ')) 
Problemi correlati