Nico Schlömer
Nico Schlömer

Reputation: 58751

Avoiding try-except nesting

Given a file of unknown file type, I'd like to open that file with one of a number of handlers. Each of the handlers raises an exception if it cannot open the file. I would like to try them all and if none succeeds, raise an exception.

The design I came up with is

filename = 'something.something'
try:
    content = open_data_file(filename)
    handle_data_content(content)
except IOError:
    try:
        content = open_sound_file(filename)
        handle_sound_content(content)
    except IOError:
        try:
            content = open_image_file(filename)
            handle_image_content(content)
        except IOError:
            ...

This cascade doesn't seem to be the right way to do it.

Any suggestions?

Upvotes: 3

Views: 310

Answers (4)

user890167
user890167

Reputation:

Is checking entirely out of the question?

>>> import urllib
>>> from mimetypes import MimeTypes

>>> guess = MimeTypes()
>>> path = urllib.pathname2url(target_file)
>>> opener = guess.guess_type(path)
>>> opener
('audio/ogg', None)

I know try/except and eafp is really popular in Python, but there are times when a foolish consistency will only interfere with the task at hand.

Additionally, IMO a try/except loop may not necessarily break for the reasons you expect, and as others have pointed out you're going to need to report the errors in a meaningful way if you want to see what's really happening as you try to iterate over file openers until you either succeed or fail. Either way, there's introspective code being written: to dive into the try/excepts and get a meaningful one, or reading the file path and using a type checker, or even just splitting the file name to get the extension... in the face of ambiguity, refuse the temptation to guess.

Upvotes: 1

Yuvika
Yuvika

Reputation: 6122

You can use Context Managers:

class ContextManager(object):
    def __init__(self, x, failure_handling): 
        self.x = x 
        self.failure_handling  = failure_handling

    def __enter__(self):
        return self.x

    def __exit__(self, exctype, excinst, exctb):
        if exctype == IOError:
            if self.failure_handling:
                fn = self.failure_handling.pop(0)
                with ContextManager(fn(filename), self.failure_handling) as context:
                     handle_data_content(context)

        return True

filename = 'something.something'
with ContextManager(open_data_file(filename), [open_sound_file, open_image_file]) as content:
handle_data_content(content)

Upvotes: 0

Paulo Bu
Paulo Bu

Reputation: 29794

Maybe you can group all the handlers and evaluate them in a for loop raising an exception at the end if none succeeded. You can also hang on to the raised exception to get some of the information back out of it as shown here:

filename = 'something.something'
handlers = [(open_data_file, handle_data_context), 
            (open_sound_file, handle_sound_content),
            (open_image_file, handle_image_content)
]
for o, h in handlers:
    try:
        o(filename)
        h(filename)
        break
    except IOError as e:
        pass
else:
    # Raise the exception we got within. Also saves sub-class information.
    raise e

Upvotes: 5

shx2
shx2

Reputation: 64308

Like the others, I also recommend using a loop, but with tighter try/except scopes.

Plus, it's always better to re-raise the original exception, in order to preserve extra info about the failure, including traceback.

openers_handlers = [ (open_data_file, handle_data_context) ]

def open_and_handle(filename):
  for i, (opener, handler) in enumerate(openers_handlers):
    try:
        f = opener(filename)
    except IOError:
        if i >= len(openers_handlers) - 1:
            # all failed. re-raise the original exception
            raise
        else:
            # try next
            continue
    else:
        # successfully opened. handle:
        return handler(f)

Upvotes: 0

Related Questions