区分复合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__
    ...
               
              
with声明不会奇迹般地打破了周围的try...except声明。