【问题标题】:Catching an exception while using a Python 'with' statement在使用 Python 'with' 语句时捕获异常
【发布时间】:2010-10-17 08:39:17
【问题描述】:

让我感到羞耻的是,我不知道如何处理 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 语句。
  • 有趣的是,Java 的 try-with-resources 语句确实 完全支持您想要的这个用例。 docs.oracle.com/javase/tutorial/essential/exceptions/…

标签: python exception-handling


【解决方案1】:
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()

【讨论】:

  • stackoverflow.com/questions/5205811/… 中所述,这里的try 块实在是太宽泛了。创建上下文管理器时的异常和 with 语句主体中的异常没有区别,因此它可能不是所有用例的有效解决方案。
  • @ncoghlan 但是您可以在with 中添加额外的try...except 块,以便更接近与open() 无关的异常源。
  • @rbaleksandar 如果我没记错的话,我的评论严格指答案中的第一个示例,其中整个 with 语句位于 try/except 块内(所以即使你有内部 try/expect块,他们让逃脱的任何异常仍然会击中外部)。道格拉斯随后添加了第二个示例来解决这种区别很重要的情况。
  • 在这个例子中文件会被关闭吗?我问是因为它是在“with”范围之外打开的。
  • @MikeCollins 退出 'with' 将关闭打开的文件,即使文件在 'with' 之前打开也是如此。
【解决方案2】:

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

【讨论】:

  • 我喜欢它,但感觉有点太黑魔法了。它对读者来说并不完全明确
  • @PaulSeeb 您为什么不定义它并避免每次需要时都这样做?它是在您的应用程序级别定义的,它与任何其他上下文管理器一样神奇。我认为使用 with 语句的人会清楚地理解它(如果您不喜欢它,函数的名称也可能更具表现力)。 “with”语句本身被设计为以这种方式工作,以定义“安全”代码块并将检查功能委托给上下文管理器(以使代码更清晰)。
  • 所有这些麻烦只是因为没有在用户代码中编写 finally 块。我开始认为我们都因 with 声明中的长期炒作症状而受苦。
  • 在python中处理异常的最好方法是写一个函数来捕获并返回它们?严重地?处理异常的 Pythonic 方式是使用 try...except 语句。
  • 这太棒了,谢谢!这是唯一不违反 try 语句中包含多于 1 行的规则的答案,并且还允许我保留 with 语句而不是单独打开和关闭连接。
【解决方案3】:

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

with 语句在没有 __future__ 导入 since Python 2.6 的情况下可用。您可以使用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 builtin 的 __exit__ 不会返回 True,所以你只需要将它嵌套在 try 中,除了块:

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

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

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

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

【讨论】:

    【解决方案4】:

    区分从复合 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__,它可以决定如何处理它(参见@987654322 @)。但是,如果 __exit__ 引发自身,则原始异常将被新异常替换。为了处理这些情况,我们可以在with 的主体中添加一个通用的except 子句来存储任何可能会被忽视的异常,并将其与稍后在最外面的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)
    

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

    PEP 343 -- The "with" Statement 指定了with 语句的等效“non-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__
        ...
    

    【讨论】:

      猜你喜欢
      • 2011-07-09
      • 2011-12-07
      • 1970-01-01
      • 1970-01-01
      • 2014-01-25
      • 1970-01-01
      • 1970-01-01
      • 2013-05-12
      • 1970-01-01
      相关资源
      最近更新 更多