使用Python'with'语句时捕获异常


293

令我感到羞耻的是,我不知道如何处理python'with'语句的异常。如果我有代码:

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

我真的很想处理“找不到文件异常”以便执行某些操作。但是我不能写

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

而且不能写

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

在try / except语句中包含“ with”,否则将不起作用:不会引发异常。为了以Python方式处理“ with”语句中的失败,我该怎么办?


您是什么意思:“在try / except语句中包含'with',否则将不起作用:不会引发异常”?一个with声明不会奇迹般地打破了周围的try...except声明。
阿兰·菲

4
有趣的是,Java的try-with-resources语句确实支持所需的这种用例。docs.oracle.com/javase/tutorial/essential/exceptions/…–
Nayuki

Answers:


256
from __future__ import with_statement

try:
    with open( "a.txt" ) as f :
        print f.readlines()
except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
    print 'oops'

如果您希望通过公开调用与工作代码进行不同的错误处理,则可以执行以下操作:

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

3
stackoverflow.com/questions/5205811/…中所述,这里的try块实在太宽泛了。创建上下文管理器时的异常与with语句主体中的异常之间没有区别,因此对于所有用例而言,它可能都不是有效的解决方案。
ncoghlan 2011年

@ncoghlan但是,您可以在其中添加额外的try...except块,with以使其更接近与之无关的异常源open()
rbaleksandar '17

1
@rbaleksandar如果我没记错的话,我的评论严格是指答案中的第一个示例,其中整个with语句位于try / except块内(因此,即使您具有内部try / expect块,它们让其转义的任何异常也会仍然打在外面)。道格拉斯随后添加了第二个示例,以解决这种区分很重要的情况。
ncoghlan

3
在此示例中,文件会关闭吗?我问是因为它是在“ with”范围之外打开的。
Mike Collins '18

6
@MikeCollins退出“ with”将关闭打开的文件,即使该文件在“ with”之前打开。
user7938784 '18

75

利用该with语句的最佳“ Pythonic”方法在PEP 343中列为示例6 ,给出了该语句的背景。

@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()

用法如下:

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

38
我喜欢它,但是感觉有点太多了。它为读者并不完全明确
保罗马斯喀特

5
@PaulSeeb为什么不定义它并在每次需要时都避免这样做?它是在您的应用程序级别定义的,它与任何其他contextmanager一样神奇。我认为使用with语句的人会清楚地理解它(如果您不喜欢它的话,函数的名称也可能更具表达性)。“ with”语句本身已被设计为以这种方式工作,以定义“安全”代码块并将检查功能委托给上下文管理器(以使代码更清晰)。

9
所有这些麻烦仅是因为未在用户代码中写入finally块。我开始认为,对于with语句,我们所有人都有很长的炒作症状。
jgomo3

1
处理python中异常的最好方法是编写一个捕获并返回它们的函数?认真吗 处理异常的pythonic方法是使用一条try...except语句。
阿兰·菲

58

使用Python'with'语句时捕获异常

从Python 2.6开始,with语句无需__future__导入即可使用。您可以使用以下命令在Python 2.5之前获得它(但现在是时候进行升级了!):

from __future__ import with_statement

这是更正您所拥有的东西。您快到了,但是with没有except子句:

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

上下文管理器的__exit__方法(如果返回)False将在完成时引发错误。如果返回True,它将抑制它。在open内置的__exit__不返回True,那么你只需要嵌套一个try,except块:

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

和标准样板:不要使用裸露的东西except:,以免发生BaseException任何其他异常和警告。至少要Exception和错误一样具体,也许要抓住IOError。只捕获您准备处理的错误。

因此,在这种情况下,您需要执行以下操作:

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

2

区分复合with语句引发的异常的可能来源

区分with语句中发生的异常非常棘手,因为它们可能起源于不同的地方。可以从以下任一位置(或其中调用的功能)引发异常:

  • ContextManager.__init__
  • ContextManager.__enter__
  • 的身体 with
  • ContextManager.__exit__

有关更多详细信息,请参见有关Context Manager Types的文档。

如果我们想区分这些不同的情况,仅将包裹with到a中try .. except是不够的。考虑以下示例(ValueError用作示例,但当然可以用任何其他异常类型代替):

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

在此,except遗嘱将捕获源自四个不同地方的所有例外,因此不允许对其进行区分。如果将上下文管理器对象的实例移到之外with,则可以区分__init__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

有效地,这仅对__init__零件有所帮助,但是我们可以添加一个额外的哨兵变量来检查with开始执行的主体(即__enter__与其他主体区别):

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

棘手的部分是区分源自BLOCK__exit__的异常,这是因为逃脱了withwill 主体的异常将传递给__exit__它,从而可以决定如何处理它(请参阅docs)。但是__exit__,如果出现异常,则将原来的例外替换为新的例外。为了处理这些情况,我们可以except在的主体中添加一个通用子句,with以存储可能会被忽略而不会被忽略的任何潜在异常,并将其与except后来在最外层捕获的异常进行比较-如果它们相同,则表示起源是BLOCK否则,它是__exit__(以防万一__exit__,通过在最外层返回true值来抑制异常except 根本不会执行)。

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)

使用PEP 343中提到的等效形式的替代方法

PEP 343-“ with”语句指定该语句的等效“ non-with”版本with。在这里,我们可以轻松地将各个部分包装在一起try ... except,从而区分出不同的潜在错误源:

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)

通常,更简单的方法就可以了

这种特殊异常处理的需求应该非常少,通常将整个包装with在一个try ... except块中就足够了。特别是如果各种错误源由不同的(自定义)异常类型指示(需要相应地设计上下文管理器),我们可以很容易地区分它们。例如:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.