简体   繁体   English

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

[英]Catching an exception while using a Python 'with' statement

To my shame, I can't figure out how to handle exception for python 'with' statement. 令我遗憾的是,我无法弄清楚如何处理python'with'语句的异常。 If I have a code: 如果我有一个代码:

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

I really want to handle 'file not found exception' in order to do somehing. 我真的想处理'文件未找到异常'以便进行处理。 But I can't write 但我不能写

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

and can't write 并且不能写

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

enclosing 'with' in a try/except statement doesn't work else: exception is not raised. 在try / except语句中包含'with'不起作用:不引发异常。 What can I do in order to process failure inside 'with' statement in a Pythonic way? 为了以Pythonic方式处理'with'语句内部的失败,我该怎么办?

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'

If you want different handling for errors from the open call vs the working code you could do: 如果您希望对打开调用与工作代码中的错误进行不同的处理,则可以执行以下操作:

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

The best "Pythonic" way to do this, exploiting the with statement, is listed as Example #6 in PEP 343 , which gives the background of the statement. 使用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()

Used as follows: 使用如下:

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

Catching an exception while using a Python 'with' statement 使用Python'with'语句时捕获异常

The with statement has been available without the __future__ import since Python 2.6 . 自Python 2.6以来 ,with语句在没有__future__导入的情况下可用。 You can get it as early as Python 2.5 (but at this point it's time to upgrade!) with: 您可以早在Python 2.5中获得它(但此时需要升级!):

from __future__ import with_statement

Here's the closest thing to correct that you have. 这是你最接近纠正的事情。 You're almost there, but with doesn't have an except clause: 你几乎没有,但with没有一个except条款:

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

A context manager's __exit__ method, if it returns False will reraise the error when it finishes. 上下文管理器的__exit__方法,如果返回False ,则在完成时将重新__exit__错误。 If it returns True , it will suppress it. 如果它返回True ,它将抑制它。 The open builtin's __exit__ doesn't return True , so you just need to nest it in a try, except block: open builtin的__exit__不会返回True ,所以你只需要在try中嵌套它,块除外:

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

And standard boilerplate: don't use a bare except: which catches BaseException and every other possible exception and warning. 和标准样板:不要使用bare except:它捕获BaseException和其他所有可能的异常和警告。 Be at least as specific as Exception , and for this error, perhaps catch IOError . 至少与Exception一样具体,对于此错误,可能会捕获IOError Only catch errors you're prepared to handle. 只捕捉你准备处理的错误。

So in this case, you'd do: 所以在这种情况下,你会这样做:

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

Differentiating between the possible origins of exceptions raised from a compound with statement 区分化合物with陈述产生的异常的可能来源

Differentiating between exceptions that occur in a with statement is tricky because they can originate in different places. 区分with语句中出现的异常是棘手的,因为它们可能来自不同的地方。 Exceptions can be raised from either of the following places (or functions called therein): 可以从以下任一位置(或其中调用的函数)引发异常:

  • ContextManager.__init__
  • ContextManager.__enter__
  • the body of the with 的身体with
  • ContextManager.__exit__

For more details see the documentation about Context Manager Types . 有关更多详细信息,请参阅有关Context Manager类型的文档。

If we want to distinguish between these different cases, just wrapping the with into a try .. except is not sufficient. 如果我们希望这些不同的情况加以区分,只是在包装with进入try .. except是不够的。 Consider the following example (using ValueError as an example but of course it could be substituted with any other exception type): 请考虑以下示例(使用ValueError作为示例,但当然可以使用任何其他异常类型替换):

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

Here the except will catch exceptions originating in all of the four different places and thus does not allow to distinguish between them. 这里except将捕获源自所有四个不同地方的例外,因此不允许区分它们。 If we move the instantiation of the context manager object outside the with , we can distinguish between __init__ and BLOCK / __enter__ / __exit__ : 如果我们将上下文管理器对象的实例化移到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

Effectively this just helped with the __init__ part but we can add an extra sentinel variable to check whether the body of the with started to execute (ie differentiating between __enter__ and the others): 实际上这只是对__init__部分的帮助,但是我们可以添加一个额外的sentinel变量来检查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

The tricky part is to differentiate between exceptions originating from BLOCK and __exit__ because an exception that escapes the body of the with will be passed to __exit__ which can decide how to handle it (see the docs ). 棘手的部分是区分源自BLOCK__exit__的异常,因为转义为with的主体的异常将传递给__exit__ ,后者可以决定如何处理它(参见文档 )。 If however __exit__ raises itself, the original exception will be replaced by the new one. 但是,如果__exit__升起,则原始异常将被新的异常替换。 To deal with these cases we can add a general except clause in the body of the with to store any potential exception that would have otherwise escaped unnoticed and compare it with the one caught in the outermost except later on - if they are the same this means the origin was BLOCK or otherwise it was __exit__ (in case __exit__ suppresses the exception by returning a true value the outermost except will simply not be executed). 为了处理这些情况,我们可以在with的主体中添加一个通用的except子句来存储任何潜在的异常,否则这些异常会被忽视,并将它与最外层捕获的异常进行比较, except后面 - 如果它们是相同的,这意味着原点是BLOCK ,否则它是__exit__ (如果__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)

Alternative approach using the equivalent form mentioned in PEP 343 使用PEP 343中提到的等效形式的替代方法

PEP 343 -- The "with" Statement specifies an equivalent "non-with" version of the with statement. PEP 343 - “同向”的声明规定的等效“非与”版本with声明。 Here we can readily wrap the various parts with try ... except and thus differentiate between the different potential error sources: 在这里,我们可以使用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)

Usually a simpler approach will do just fine 通常一种更简单的方法就可以了

The need for such special exception handling should be quite rare and normally wrapping the whole with in a try ... except block will be sufficient. 需要这样的特殊的异常处理应该是很罕见的,通常包裹整个withtry ... except块就足够了。 Especially if the various error sources are indicated by different (custom) exception types (the context managers need to be designed accordingly) we can readily distinguish between them. 特别是如果各种错误源由不同的(自定义)异常类型指示(上下文管理器需要相应地设计),我们可以很容易地区分它们。 For example: 例如:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...

resort to the standard exception handling 诉诸于标准的异常处理

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM