2012-09-24 15 views
17

Possiedo diversi file che potrebbero contenere i miei dati; possono essere compressi in diversi modi, quindi per aprirli ho bisogno di usare file(), gzip.GzipFile() e altri che restituiscono anche un oggetto file (che supporta l'interfaccia with).Diversi `with`s in` try`s

voglio provare ciascuno di loro fino a quando si riesce ad apertura, così ho potuto fare qualcosa di simile

try: 
    with gzip.GzipFile(fn + '.gz') as f: 
    result = process(f) 
except (IOError, MaybeSomeGzipExceptions): 
    try: 
    with xCompressLib.xCompressFile(fn + '.x') as f: 
     result = process(f) 
    except (IOError, MaybeSomeXCompressExceptions): 
    try: 
     with file(fn) as f: 
     result = process(f) 
    except IOError: 
     result = "some default value" 

che ovviamente non è fattibile nel caso in cui ho decine di possibili varianti di compressione. (Il nesting diventerà sempre più profondo, il codice sembra sempre molto simile.)

C'è un modo più bello per spiegarlo?

MODIFICA: Se possibile mi piacerebbe avere il process(f) fuori dal tentativo/eccetto anche per evitare l'accidentale cattura di eccezioni sollevate nello process(f).

risposta

7

mi piacerebbe scrivere un gestore di contesto personalizzato:

from contextlib import contextmanager 

filetypes = [('.gz', gzip.GzipFile, (IOError, MaybeSomeGzipExceptions)), 
      ('.x', xCompressLib.xCompressFile, (IOError, MaybeSomeXCompressExceptions))] 

@contextmanager 
def open_compressed(fn): 
    f = None 
    try: 
     for ext, cls, exs in filetypes: 
      try: 
       f = cls(fn + ext) 
      except exs: 
       pass 
      else: 
       break 
     yield f 
    finally: 
     if f is not None: 
      f.close() 

with open_compressed(fn) as f: 
    result = "some default value" if f is None else process(f) 

O forse solo una funzione che restituisce un gestore di contesto:

filetypes = [('.gz', gzip.GzipFile, (IOError, MaybeSomeGzipExceptions)), 
      ('.x', xCompressLib.xCompressFile, (IOError, MaybeSomeXCompressExceptions))] 

class UnknownCompressionFormat(Exception): 
    pass 

def open_compressed(fn): 
    for ext, cls, exs in filetypes: 
     try: 
      return cls(fn + ext) 
     except exs: 
      pass 
    raise UnknownCompressionFormat 

try: 
    with open_compressed(fn) as f: 
     result = process(f) 
except UnknownCompressionFormat: 
    result = "some default value" 
+0

Penso che questo approccio mi piaccia ancor più dell'altra. – Alfe

+2

Bello. Mi piace particolarmente il secondo approccio. Suggerirei comunque che entrambi gli approcci generassero un'eccezione per UnknownCompressionFormat. –

9

Sì, si potrebbe mettere tutte le varianti attraverso un elenco e provare fino a quando uno di loro lavora, in tal modo non-nesting il tuo codice:

def process_gzip(fn): 
    with gzip.GzipFile(fn + '.gz') as f: 
     return process(f) 

def process_xlib(fn): 
    with xCompressLib.xCompressFile(fn + '.x') as f: 
     return process(f) 

def process_builtin(fn): 
    with file(fn) as f: 
     return process(f) 

process_funcs = [process_gzip, process_xlib, process_builtin] 

#processing code: 

for process_f in process_funcs: 
    try: 
     result = process_f(fn) 
     break 
    except IOError: 
     #error reading the file, keep going 
     continue 
    except: 
     #processing error, re-raise the exception 
     raise 

Oppure, per ridurre la quantità di codice che potrebbe fare un process_func fabbrica, dal momento che tutti hanno la stessa forma:

def make_process_func(constructor, filename_transform): 
    with constructor(filename_transform) as f: 
     return process(f) 

process_funcs = [ 
    make_process_func(gzip.GzipFile, lambda fn: fn + '.gz'), 
    make_process_func(xCompressLib.xCompressFile, lambda fn: fn + '.x'), 
    make_process_func(file, lambda fn: fn), 
] 
+0

Ho una modifica alla mia domanda: io 'Mi piacerebbe avere il 'process (f)' fuori dal tentativo/tranne se possibile. – Alfe

+1

solo l'apertura del file potrebbe non causare un errore IOError. leggere il file potrebbe farlo anche quindi vorrete leggere tutti i dati e poi elaborarli se volete separare IOError dall'errore di elaborazione, no? – Claudiu

+1

L'unica cosa che vorrei aggiungere qui è che probabilmente dovresti tenere in mano le tuple che contengono le varie eccezioni accettabili. per esempio. 'prova: ; eccetto passable_exceptions: pass' – mgilson

5

Sarebbe questo lavoro:

extensions = [('.gz', gzip.GzipFile, (IOError, MaybeSomeGzipExceptions)), 
       ('.x', xCompressLib.xCompressFile, (IOError, MaybeSomeXCompressExceptions))] # and other such entries 
processed = False 
for ext, (compressor, errors) in extensions.iteritems(): 
    try: 
     with compressor(fn+ext) as f: 
      try: 
       result = process(f) 
       processed = True 
       break 
      except: 
       raise 
    except errors: 
     pass 
if not processed: 
    result = "some default value" 

Speranza che aiuta

+0

Ancora una volta, ho un EDIT alla mia domanda: è possibile ottenere il 'process (f)' dal try/catch (che dovrebbe comunque catturare eccezioni nella roba 'with')? – Alfe

+0

Questo è quello che farei anch'io, ma è possibile che l'ordine sia importante. – mgilson

+0

L'ordine conta davvero, ma l'uso di una lista di tuple invece di una dict non è un problema. – Alfe