我应该如何捕获 Python 中的 `with open(filename)` 引发的异常?

How should I catch exceptions raised by `with open(filename)` in Python?

在 Python 中尝试打开文件的行为可能会引发异常。如果我使用 with 语句打开文件,是否可以捕获 open 调用和相关 __enter__ 调用抛出的异常,而不捕获 [=12] 中的代码引发的异常=]块?

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 older one的不同之处在于,较早的问题是关于编写上下文管理器,而不是使用熟悉的with open

如果错误与打开文件有关(例如,如果文件不存在),它将由对 open 本身的调用引发,而不是 __enter__ .在这种情况下,您可以通过将 open 调用与 with 块分开来捕获它:

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)

正如您链接到的问题(以及其他相关问题)所暗示的那样,您无法真正将 __enter__ 中的异常与 with 块中的异常区分开来。然而,正是出于这个原因,在大多数情况下,试图做可能在 __enter__ 中引发异常的复杂事情的上下文管理器可能无论如何都是一个脆弱的设计。我认为 file.__enter__ 只是 returns 文件(如 this page 所建议),尽管我找不到任何保证这一点的文档。

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?

是:

#!/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')

使用默认的 open() 函数,__enter__() 不应引发任何有趣的异常,因此可以简化代码:

#!/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')

在Python3中,IOError是OSError的别名。要验证,运行 代码:

IOError is OSError
---
True

OSError 是文件 I/O 异常的父 class。

      +-- 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]

因此,如果需要详细信息,请捕获 OSError 并检查确切的 class。

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'