Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Several `with`s in `try`s

I have several possible files which could hold my data; they can be compressed in different ways, so to open them I need to use file(), gzip.GzipFile() and other which also return a file object (supporting the with interface).

I want to try each of them until one succeeds in opening, so I could do something like

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"

which obviously isn't feasible in case I have dozens of possible compression variants. (The nesting will get deeper and deeper, the code always looking very much alike.)

Is there a nicer way to spell this out?

EDIT: If possible I'd like to have the process(f) out of the try/except as well to avoid accidental catching of exceptions raised in the process(f).

like image 573
Alfe Avatar asked Sep 24 '12 12:09

Alfe


People also ask

What is the plural for try?

try (plural tries)

Is several singular or plural?

The indefinite pronouns both, few, many, others, and several are always plural.

Do we add s after many?

RULE 3: Use S with several, a lot of, and many They determine the amount of something. They mean more than one. So, when using these adjectives, you need the plural form of the noun, and so you must put the S at the end.


2 Answers

Yea, you could put all your variants through a list and try them until one of them works, thus un-nesting your code:

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

Or, to reduce amount of code you could make a process_func factory, since they all have the same form:

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),
]
like image 137
Claudiu Avatar answered Oct 14 '22 03:10

Claudiu


I'd write a custom context manager:

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)

Or possibly just a function that returns a context manager:

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"
like image 31
ecatmur Avatar answered Oct 14 '22 05:10

ecatmur