3

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?

4
  • 3
    I would recommend codereview.stackoverflow.com Commented Jul 2, 2014 at 19:29
  • Is there any way to check what type the file is before you try processing it? Magic numbers maybe? Commented Jul 2, 2014 at 19:30
  • Using with will remove one or two levels of try/except Commented Jul 2, 2014 at 19:31
  • 3
    for opener, handler in [(open_data_file, handle_data_content), ...]:? Commented Jul 2, 2014 at 19:33

4 Answers 4

5

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 
Sign up to request clarification or add additional context in comments.

5 Comments

for/else would be a better way of doing what you're doing with succeed.
Seems true, I'm gonna look at it. I don't use for/else much often. Thanks for the comment.
Worth noting: this does not preserve all exception info; merely the last exception raised in the loop. Since exceptions are being raised by improperly-matched handlers, it might make more sense to raise a more explicit exception in the else clause; e.g. IOError('File cannot be read by any known handler').
Yes I agree, anyways that was not really the main part of the question. I rather left the raise part opened. Thanks also to @pydsigner for his edits and suggestions.
I also noticed recently that, in Python 3, this code should display the original traceback as well, perfectly reproducing the original error message.
1

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.

Comments

0

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) 

Comments

0

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) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.