5

The act of attempting to open a file in Python can throw an exception. If I'm opening the file using the with statement, can I catch exceptions thrown by the open call and the related __enter__ call without catching exceptions raised by the code within the with block?

try:
    with open("some_file.txt") as infile:
        print("Pretend I have a bunch of lines here and don't want the `except` below to apply to them")
        # ...a bunch more lines of code here...
except IOError as ioe:
    print("Error opening the file:", ioe)
    # ...do something smart here...

This question is different from this older one in that the older one is about writing a context manager, rather than using the familiar with open.

Community
  • 1
  • 1
kuzzooroo
  • 6,788
  • 11
  • 46
  • 84
  • 1
    Do you really want to catch exceptions thrown by `__enter__`, or do you want to catch exceptions thrown by the `open` call itself? – BrenBarn Sep 21 '15 at 01:14
  • @BrenBarn, good point. I wasn't considering those as different, but I suppose they must be. I guess I want exceptions from either place, but I'm more concerned with whatever piece of code would be raising exception's like the `IOError`. – kuzzooroo Sep 21 '15 at 01:16

3 Answers3

7

can I catch exceptions thrown by the open call and the related __enter__ call without catching exceptions raised by the code within the with block?

Yes:

#!/usr/bin/env python3
import contextlib

stack = contextlib.ExitStack()
try:
    file = stack.enter_context(open('filename'))
except OSError as e:
    print('open() or file.__enter__() failed', e)
else:
    with stack:
        print('put your with-block here')

with the default open() function, __enter__() shouldn't raise any interesting exceptions and therefore the code could be simplified:

#!/usr/bin/env python    
try:
    file = open('filename')
except OSError as e:
    print('open() failed', e)
else:
    with file:
        print('put your with-block here')
jfs
  • 399,953
  • 195
  • 994
  • 1,670
6

If the error has to do with opening the file (for instance, if the file doesn't exist), it will be raised by the call to open itself, not by __enter__. In this case you can catch it by separating the open call from the with block:

try:
    myFile = open('some_file.txt')
except IOError:
    print("Oh my snakes and garters!")

with myFile:
    # do stuff

# This will be True
print(myFile.closed)

As the question you linked to (and other related ones) suggest, you can't really separate exceptions in __enter__ from exceptions in the with block. However, for that very reason, in most cases a context manager that tries to do complicated stuff that might raise an exception in __enter__ is probably a fragile design anyway. I think file.__enter__ just returns the file (as suggested by this page), although I can't find any documentation guaranteeing this.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
4

In Python 3, IOError is an alias of OSError. To verify, run the code:

IOError is OSError
---
True

OSError is the parent class of the file I/O exceptions.

      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
OSError.__subclasses__()
---
[ConnectionError,
 BlockingIOError,
 ChildProcessError,
 FileExistsError,
 FileNotFoundError,
 IsADirectoryError,
 NotADirectoryError,
 InterruptedError,
 PermissionError,
 ProcessLookupError,
 TimeoutError,
 io.UnsupportedOperation,
 signal.ItimerError,
 socket.herror,
 socket.gaierror,
 socket.timeout,
 ssl.SSLError,
 shutil.Error,
 shutil.SpecialFileError,
 shutil.ExecError,
 shutil.ReadError,
 urllib.error.URLError,
 gzip.BadGzipFile]

Hence, catch the OSError and check the exact class if detail is requied.

try:
    with open('hoge') as f:
        pass
except OSError as e:
    print(f"{type(e)}: {e}")
---
<class 'FileNotFoundError'>: [Errno 2] No such file or directory: 'hoge'
mon
  • 18,789
  • 22
  • 112
  • 205