简体   繁体   中英

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?

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

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 .

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)

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM