Несколько `с` в `try`
У меня есть несколько возможных файлов, которые могут содержать мои данные; они могут быть сжаты по-разному, поэтому для их открытия мне нужно использовать file()
, gzip.GzipFile()
и другие, которые также возвращают объект файла (поддерживающий интерфейс with
).
Я хочу попробовать каждый из них, пока вам не удастся открыть его, поэтому я мог бы сделать что-то вроде
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"
что, очевидно, не представляется возможным в случае, если у меня есть десятки возможных вариантов сжатия. (Гнездо будет становиться все глубже и глубже, код всегда выглядит очень похожим.)
Есть ли лучший способ написать это?
EDIT: если возможно, я хотел бы иметь process(f)
из try/except, кроме того, чтобы избежать случайного захвата исключений, поднятых в process(f)
.
Ответы
Ответ 1
Я бы написал пользовательский менеджер контекста:
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)
Или, возможно, просто функция, которая возвращает диспетчер контекста:
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"
Ответ 2
Да, вы можете поместить все свои варианты через список и попробовать их, пока один из них не будет работать, тем самым не разложив ваш код:
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
Или, чтобы уменьшить количество кода, вы можете сделать process_func factory, так как все они имеют одинаковую форму:
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),
]
Ответ 3
Будет ли это работать:
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"
Надеюсь, что поможет