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

gri*_*yvp 258 python exception-handling

令我遗憾的是,我无法弄清楚如何处理python'with'语句的异常.如果我有一个代码:

with open("a.txt") as f:
    print f.readlines()
Run Code Online (Sandbox Code Playgroud)

我真的想处理'文件未找到异常'以便进行处理.但我不能写

with open("a.txt") as f:
    print f.readlines()
except:
    print 'oops'
Run Code Online (Sandbox Code Playgroud)

并且不能写

with open("a.txt") as f:
    print f.readlines()
else:
    print 'oops'
Run Code Online (Sandbox Code Playgroud)

在try/except语句中包含'with'不起作用:不引发异常.为了以Pythonic方式处理'with'语句内部的失败,我该怎么办?

Dou*_*der 235

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'
Run Code Online (Sandbox Code Playgroud)

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

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()
Run Code Online (Sandbox Code Playgroud)

  • @MikeCollins退出“ with”将关闭打开的文件,即使该文件在“ with”之前打开。 (4认同)
  • 如http://stackoverflow.com/questions/5205811/catching-an-exception-while-using-a-python-with-statement-part-2中所述,此处的try块实际上太宽泛了.创建上下文管理器时的异常与with语句的主体中的异常没有区别,因此它可能不是所有用例的有效解决方案. (3认同)
  • 在此示例中,文件会关闭吗?我问是因为它是在“ with”范围之外打开的。 (2认同)
  • 这个答案不适用于 Python 3 (2认同)

小智 66

使用该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()
Run Code Online (Sandbox Code Playgroud)

使用如下:

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

  • 我喜欢它,但感觉有点太黑魔法了.它对读者来说并不完全明确 (30认同)
  • 所有这些麻烦只是因为没有在用户代码中写finally块.我开始认为我们都因为声明中的长期炒作症状而受苦. (7认同)
  • @PaulSeeb为什么你不定义它并且每次你需要时都不会这样做?它是在您的应用程序级别定义的,它与任何其他上下文管理器一样神奇.我认为使用with语句的人会清楚地理解它(如果你不喜欢它,函数的名称也可能更具表现力)."with"语句本身已设计为以这种方式工作,以定义"安全"代码块并将检查函数委托给上下文管理器(以使代码更清晰). (5认同)
  • 在python中处理异常的最好方法是编写一个捕获并返回它们的函数?严重地?处理异常的 Pythonic 方法是使用 `try...except` 语句。 (3认同)

Aar*_*all 56

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

自Python 2.6以来,with语句一直没有__future__导入.您可以早在Python 2.5中获得它(但此时需要升级!):

from __future__ import with_statement
Run Code Online (Sandbox Code Playgroud)

这是你最接近纠正的事情.你快到了,但with没有except条款:

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

上下文管理器的__exit__方法,如果它返回False将在完成时重新加载错误.如果它返回True,它将抑制它.该open内建的__exit__不返回True,那么你只需要嵌套一个try,except块:

try:
    with open("a.txt") as f:
        print(f.readlines())
except Exception as error: 
    print('oops')
Run Code Online (Sandbox Code Playgroud)

标准样板:不要使用裸露的except:捕获BaseException和其他可能的异常和警告.至少与具体的一样Exception,对于这个错误,也许是捕获IOError.只捕捉你准备处理的错误.

所以在这种情况下,你会这样做:

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


a_g*_*est 9

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

区分with语句中发生的异常很棘手,因为它们可能来自不同的地方。可以从以下任何一个地方(或其中调用的函数)引发异常:

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

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

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

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)
Run Code Online (Sandbox Code Playgroud)

这里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
Run Code Online (Sandbox Code Playgroud)

实际上,这只是对__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
Run Code Online (Sandbox Code Playgroud)

棘手的部分是区分源自BLOCK__exit__因为转义with遗嘱主体的异常__exit__可以决定如何处理它(请参阅文档)。但是__exit__,如果引发自身,则原始异常将被新异常替换。为了处理这些情况,我们可以except在 的主体中添加一个通用子句with来存储任何可能会被忽视的潜在异常,并将其与except稍后捕获的最外面的异常进行比较- 如果它们相同,则意味着起源是BLOCK否则它是__exit__(以防__exit__通过在最外层返回真值来抑制异常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)
Run Code Online (Sandbox Code Playgroud)

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

PEP 343——“with”语句指定了该语句的等效“非 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)
Run Code Online (Sandbox Code Playgroud)

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

这种特殊异常处理的需求应该很少见,通常将整个包裹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__
    ...
Run Code Online (Sandbox Code Playgroud)