With 语句的异常未传递给自定义上下文管理器 __exit__

Exception from With statement is not passed to custom context manager __exit__

我正在试验 With 语句和自定义上下文管理器。我不明白为什么执行 With 语句时引发的异常没有传递到我的自定义上下文管理器 __exit__()?

带有自定义上下文管理器的class如下:

class with_point():

    def __init__(self, numer, denom):
        self.numer = numer
        self.denom = denom
    
    def __enter__(self):
        print("This is entry point")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("An error of type {} with value {} happened".format(exc_type,exc_value))
        if exc_type is None:
            return True
        else:
            return False

class应该接受两个数值,以后用作分数的分子和分母。

接下来我 运行 使用 With 语句创建 with_point 类型的对象并计算分数的代码:

from with_class import with_point
with with_point(8,0) as wp:
    try:
        fr = wp.numer / wp.denom
    except Exception as e:
        print(e)

当我 运行 后一个代码输出如下:

This is entry point
division by zero
An error of type None with value None happened

这与我的预期相反,因为语言参考 indicates

The context manager’s __exit__() method is invoked. If an exception caused the suite to be exited, its type, value, and traceback are passed as arguments to __exit__(). Otherwise, three None arguments are supplied.

我希望根据文档将 除以零 异常传递给 __exit__ 上下文管理器。然而,这并没有发生,上下文管理器收到 None.

我在这里搜索了一些类似的问题,找到了 and this。首先 link 没有帮助,因为它建议 return False 从 __exit__ 如果在 With 语句中引发异常,这正是我所做的。第二个 link 建议在 __exit__ 处理程序中引发一个新异常,这可能是个好主意,但它没有说明为什么 'None' 异常从 With 语句而不是`传递到上下文管理器除以零异常。

我需要在 (1) 自定义上下文管理器 (2) With 语句 (3) 中更改什么,以便将 DivideByZero 异常从 With 语句传递给 __exit__ 处理程序?

如文档所述:

If an exception caused the suite to be exited, its type, value, and traceback are passed as arguments to __exit__(). Otherwise, three None arguments are supplied.

在你的案例中,“被零除”例外并没有导致诉讼退出,因为你在 except Exception as e: 部分除外!所以它被静音了。如果你删除 try/except,你会看到它传递给了 __exit__,因为它现在在 with 语句中未被处理,它会导致 suit 退出。

要使 __exit__ 中的异常静音,文档说:

If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a "true" value. Otherwise, the exception will be processed normally upon exit from this method.

最好的方法是:

class with_point:

    def __init__(self, numer, denom):
        self.numer = numer
        self.denom = denom

    def __enter__(self):
        print("This is entry point")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("An error of type '{}' with value '{}' happened".format(exc_type, exc_value))
        return isinstance(exc_value, ZeroDivisionError)


with with_point(8, 0) as wp:
    fr = wp.numer / wp.denom

这就是您通常处理上下文管理器的方式。如果异常是 ZeroDivisionError 它将被沉默,否则它会传播。尝试:

with with_point(8, '0') as wp:
    fr = wp.numer / wp.denom