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

时间:2009-04-03 13:00:26

标签: python exception-handling

令我遗憾的是,我无法弄清楚如何处理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'不起作用:不引发异常。为了以Pythonic方式处理'with'语句内部的失败,我该怎么做?

5 个答案:

答案 0 :(得分:214)

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

答案 1 :(得分:63)

使用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")

答案 2 :(得分:49)

  

使用Python'with'语句

时捕获异常

在没有__future__导入since Python 2.6的情况下,可以使用with语句。您可以将其作为early as 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中,块除外:

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

答案 3 :(得分:2)

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

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

  • ContextManager.__init__
  • ContextManager.__enter__
  • with的正文
  • ContextManager.__exit__

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

如果我们想区分这些不同的情况,仅将with包装到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__的异常,因为逃避with主体的异常将传递给__exit__,后者可以决定如何处理(请参见the docs)。但是,如果__exit__自身出现,则原始异常将被新异常替换。为了处理这些情况,我们可以在except的主体中添加一个通用的with子句,以存储任何可能会被忽略的潜在异常,并将其与最外层{{1 }}-如果它们相同,则意味着原点是except或其他地方是BLOCK(以防__exit__通过在最外面的{{1 }}将不会被执行)。

__exit__

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

PEP 343 -- The "with" Statement指定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) 包装各个部分,从而区分出不同的潜在错误源:

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

答案 4 :(得分:-3)

采用标准异常处理

try:
    with open("a.txt") as f:
        #business as usual
except Exception as e:
    print "oops, handle exception: ", e