Подтвердить что ты не робот

Несколько `с` в `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).

4b9b3361

Ответ 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"

Надеюсь, что поможет