grigoryvp
grigoryvp

Reputation: 42463

Catching an exception while using a Python 'with' statement

I can't figure out how to handle exception for python 'with' statement. If I have a code:

with open("a.txt") as f:
    print f.readlines()

I really want to handle 'file not found exception' in order to do something. But I can't write

with open("a.txt") as f:
    print f.readlines()
except:
    print 'oops'

and can't write

with open("a.txt") as f:
    print f.readlines()
else:
    print 'oops'

Enclosing with in a try/except statement doesn't work either, and an exception is not raised. What can I do in order to process failure inside with statement in a Pythonic way?

Upvotes: 422

Views: 354163

Answers (7)

halt9k
halt9k

Reputation: 951

Nested contexts here are quite clean and readable:

@contextlib.contextmanager
def catch(err_types=Exception, on_exception=None):
    try:
        yield
    except err_types as e:
        if on_exception:
            on_exception(e)



def on_missing_file(e):
    print("Expected error")

with catch(FileNotFoundError, on_missing_file), open('test.txt') as f:
    raise Exception("Unexpected error")

can even be extended quite well with extra contexts (optional yield example) or on_missing_file shortened to lambda.

Upvotes: 1

Lars H
Lars H

Reputation: 69

Here's a modified solution from geeksforgeeks 'with' description:

class MessageWriter(object):
def __init__(self, file_name):
    self.file_name = file_name

def __enter__(self):
    try:
        self.file = open(self.file_name, 'w')
    except IOError:
        print("An open exception occurred")
    return self.file

def __exit__(self, *args):
    self.file.close()

with MessageWriter('my_file.txt') as xfile:
try:
    xfile.write('hello world')
except:
    print("A write exception occurred")

Upvotes: 1

Douglas Leeder
Douglas Leeder

Reputation: 53310

This solution will keep the with-block-code outside of the try-except-clause.

try:
    f = open('foo.txt')
except FileNotFoundError:
    print('error')
else:
    with f:
        print f.readlines()

Upvotes: 366

user765144
user765144

Reputation:

The best "Pythonic" way to do this, exploiting the with statement, is listed as Example #6 in PEP 343, which gives the background of the statement.

from contextlib import contextmanager

@contextmanager
def opened_w_error(filename, mode="r"):
    try:
        f = open(filename, mode)
    except IOError, err:
        yield None, err
    else:
        try:
            yield f, None
        finally:
            f.close()

Used as follows:

with opened_w_error("/etc/passwd", "a") as (f, err):
    if err:
        print "IOError:", err
    else:
        f.write("guido::0:0::/:/bin/sh\n")

Upvotes: 118

Aaron Hall
Aaron Hall

Reputation: 394775

Catching an exception while using a Python 'with' statement

The with statement has been available without the __future__ import since Python 2.6. You can get it as early as Python 2.5 (but at this point it's time to upgrade!) with:

from __future__ import with_statement

Here's the closest thing to correct that you have. You're almost there, but with doesn't have an except clause:

with open("a.txt") as f: 
    print(f.readlines())
except:                    # <- with doesn't have an except clause.
    print('oops')

A context manager's __exit__ method, if it returns False will reraise the error when it finishes. If it returns True, it will suppress it. The open builtin's __exit__ doesn't return True, so you just need to nest it in a try, except block:

try:
    with open("a.txt") as f:
        print(f.readlines())
except Exception as error: 
    print('oops')

And standard boilerplate: don't use a bare except: which catches BaseException and every other possible exception and warning. Be at least as specific as Exception, and for this error, perhaps catch IOError. Only catch errors you're prepared to handle.

So in this case, you'd do:

>>> try:
...     with open("a.txt") as f:
...         print(f.readlines())
... except IOError as error: 
...     print('oops')
... 
oops

Upvotes: 85

randomrabbit
randomrabbit

Reputation: 322

This is the recommendation from the contextlib docs for Python 3:

from contextlib import ExitStack

stack = ExitStack()
try:
    f = stack.enter_context(open("a.txt"))
except FileNotFoundError:
    print("oops")
else:
    with stack:
        f.readlines()

Upvotes: 3

a_guest
a_guest

Reputation: 36239

Differentiating between the possible origins of exceptions raised from a compound with statement

Differentiating between exceptions that occur in a with statement is tricky because they can originate in different places. Exceptions can be raised from either of the following places (or functions called therein):

  • ContextManager.__init__
  • ContextManager.__enter__
  • the body of the with
  • ContextManager.__exit__

For more details see the documentation about Context Manager Types.

If we want to distinguish between these different cases, just wrapping the with into a try .. except is not sufficient. Consider the following example (using ValueError as an example but of course it could be substituted with any other exception type):

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)

Here the except will catch exceptions originating in all of the four different places and thus does not allow to distinguish between them. If we move the instantiation of the context manager object outside the with, we can distinguish between __init__ and BLOCK / __enter__ / __exit__:

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        with mgr:
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        # At this point we still cannot distinguish between exceptions raised from
        # __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
        pass

Effectively this just helped with the __init__ part but we can add an extra sentinel variable to check whether the body of the with started to execute (i.e. differentiating between __enter__ and the others):

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        entered_body = False
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        else:
            # At this point we know the exception came either from BLOCK or from __exit__
            pass

The tricky part is to differentiate between exceptions originating from BLOCK and __exit__ because an exception that escapes the body of the with will be passed to __exit__ which can decide how to handle it (see the docs). If however __exit__ raises itself, the original exception will be replaced by the new one. To deal with these cases we can add a general except clause in the body of the with to store any potential exception that would have otherwise escaped unnoticed and compare it with the one caught in the outermost except later on - if they are the same this means the origin was BLOCK or otherwise it was __exit__ (in case __exit__ suppresses the exception by returning a true value the outermost except will simply not be executed).

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    entered_body = exc_escaped_from_body = False
    try:
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
            except Exception as err:  # this exception would normally escape without notice
                # we store this exception to check in the outer `except` clause
                # whether it is the same (otherwise it comes from __exit__)
                exc_escaped_from_body = err
                raise  # re-raise since we didn't intend to handle it, just needed to store it
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        elif err is exc_escaped_from_body:
            print('BLOCK raised:', err)
        else:
            print('__exit__ raised:', err)

Alternative approach using the equivalent form mentioned in PEP 343

PEP 343 -- The "with" Statement specifies an equivalent "non-with" version of the with statement. Here we can readily wrap the various parts with try ... except and thus differentiate between the different potential error sources:

import sys

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        value = type(mgr).__enter__(mgr)
    except ValueError as err:
        print('__enter__ raised:', err)
    else:
        exit = type(mgr).__exit__
        exc = True
        try:
            try:
                BLOCK
            except TypeError:
                pass
            except:
                exc = False
                try:
                    exit_val = exit(mgr, *sys.exc_info())
                except ValueError as err:
                    print('__exit__ raised:', err)
                else:
                    if not exit_val:
                        raise
        except ValueError as err:
            print('BLOCK raised:', err)
        finally:
            if exc:
                try:
                    exit(mgr, None, None, None)
                except ValueError as err:
                    print('__exit__ raised:', err)

Usually a simpler approach will do just fine

The need for such special exception handling should be quite rare and normally wrapping the whole with in a try ... except block will be sufficient. Especially if the various error sources are indicated by different (custom) exception types (the context managers need to be designed accordingly) we can readily distinguish between them. For example:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...

Upvotes: 20

Related Questions