Python“raise from”用法

jqjz2hbq  于 2023-04-08  发布在  Python
关注(0)|答案(3)|浏览(288)

Python中的raiseraise from有什么区别?

try:
    raise ValueError
except Exception as e:
    raise IndexError

从而产生

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError
IndexError

try:
    raise ValueError
except Exception as e:
    raise IndexError from e

从而产生

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError from e
IndexError
ffx8fchx

ffx8fchx1#

不同之处在于,当你使用from时,* __cause__属性 * 被设置,并且消息声明异常是 * 由 * 直接引起的。如果你省略了from,那么就不会设置__cause__,但是 * __context__属性 * 也可能被设置,然后回溯将上下文显示为 * 在处理其他事件期间 *。
如果在异常处理程序中使用raise,则会设置__context__;如果你在其他地方使用了raise,也没有设置__context__
如果设置了__cause__,则在异常上也设置了__suppress_context__ = True标志;当__suppress_context__设置为True时,打印追溯时将忽略__context__
当从一个异常处理程序中引发时,如果你 * 不 * 想显示上下文(不想在处理另一个异常发生 * 消息期间显示 *),那么使用raise ... from None__suppress_context__设置为True
换句话说,Python在异常上设置了一个 context,这样你就可以内省一个异常是在哪里引发的,让你看看是否有另一个异常被它替换了。你也可以给一个异常添加一个 cause,让回溯对另一个异常显式显示(使用不同的措辞),并且上下文被忽略(但在调试时仍然可以进行内省)。使用raise ... from None可以禁止打印上下文。
请参阅raise语句文档:
from子句用于异常链接:如果给定,第二个 expression 必须是另一个异常类或示例,然后将作为__cause__属性(可写)附加到引发的异常。如果引发的异常未被处理,则两个异常都将被打印:

>>> try:
...     print(1 / 0)
... except Exception as exc:
...     raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

如果在异常处理程序或finally子句中引发异常,则类似的机制会隐式工作:然后将前一个异常附加为新异常的__context__属性:

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

有关附加到异常的上下文和原因信息的详细信息,请参阅内置异常文档。

vnzz0bqm

vnzz0bqm2#

2005年,PEP 3134, Exception Chaining and Embedded Tracebacks引入了异常链:

    • 隐式链接 *,带有显式raise EXCEPTION或隐式提升(__context__属性);
    • 显式链接 *,带有显式raise EXCEPTION from CAUSE__cause__属性)。

动机

在处理一个异常(异常A)的过程中,可能会发生另一个异常(异常B)。在今天的Python(版本2.4)中,如果发生这种情况,异常B会向外传播,异常A会丢失。为了调试问题,了解这两个异常是很有用的。__context__属性会自动保留此信息。
有时候,异常处理程序有意地重新引发一个异常是很有用的,可以提供额外的信息,也可以将异常转换为另一种类型。__cause__属性提供了一种显式的方法来记录异常的直接原因。
[...]

隐式异常链

下面是一个示例来说明__context__属性:

def compute(a, b):
    try:
        a/b
    except Exception, exc:
        log(exc)

def log(exc):
    file = open('logfile.txt')  # oops, forgot the 'w'
    print >>file, exc
    file.close()

调用compute(0, 0)会导致ZeroDivisionErrorcompute()函数捕获此异常并调用log(exc),但log()函数在尝试写入未打开的文件时也会引发异常。
在今天的Python中,compute()的调用者会被抛出一个IOErrorZeroDivisionError丢失了,通过建议的修改,IOError的示例有了一个额外的__context__属性来保留ZeroDivisionError
[...]

显式异常链

异常对象上的__cause__属性总是初始化为None。它由raise语句的新形式设置:

raise EXCEPTION from CAUSE

这相当于:

exc = EXCEPTION
exc.__cause__ = CAUSE
raise exc

在下面的示例中,数据库提供了几种不同类型的存储的实现,文件存储是其中的一种。数据库设计人员希望错误以DatabaseError对象的形式传播,这样客户端就不必知道特定于存储的详细信息,但又不希望丢失底层的错误信息。

class DatabaseError(Exception):
    pass

class FileDatabase(Database):
    def __init__(self, filename):
        try:
            self.file = open(filename)
        except IOError, exc:
            raise DatabaseError('failed to open') from exc

如果对open()的调用引发异常,则问题将被报告为DatabaseError,其__cause__属性显示IOError是原始原因。

增强报表

默认的异常处理器将被修改为报告链式异常。通过__cause____context__属性遍历异常链,__cause__优先。为了与追溯的时间顺序保持一致,最后显示最近引发的异常;也就是说,显示从最内层异常的描述开始,并将链备份到最外层异常。追溯的格式与通常一样,其中一行:
上述异常是导致以下异常的直接原因:

在处理上述异常的过程中,又出现了一个异常:
在回溯之间,取决于它们分别是由__cause__还是__context__链接的。下面是过程的草图:

def print_chain(exc):
    if exc.__cause__:
        print_chain(exc.__cause__)
        print '\nThe above exception was the direct cause...'
    elif exc.__context__:
        print_chain(exc.__context__)
        print '\nDuring handling of the above exception, ...'
    print_exc(exc)

[...]
2012年,PEP 415, Implement Context Suppression with Exception Attributes引入了显式raise EXCEPTION from None__suppress_context__属性)的异常上下文抑制。

提案

将引入BaseException上的新属性__suppress_context__。每当设置__cause__时,__suppress_context__将被设置为True。特别地,raise exc from cause语法将exc.__suppress_context__设置为True。异常打印代码将检查该属性以确定是否打印上下文和原因。__cause__将恢复其原始用途和价值。
具有print_line_and_file异常属性的__suppress_context__具有优先级。
总而言之,raise exc from cause相当于:

exc.__cause__ = cause
raise exc

其中exc.__cause__ = cause隐式设置exc.__suppress_context__
因此,在PEP 415中,PEP 3134中为缺省异常处理程序(其任务是报告异常)给出的过程的草图变为如下:

def print_chain(exc):
    if exc.__cause__:
        print_chain(exc.__cause__)
        print '\nThe above exception was the direct cause...'
    elif exc.__context__ and not exc.__suppress_context__:
        print_chain(exc.__context__)
        print '\nDuring handling of the above exception, ...'
    print_exc(exc)
cpjpxq1n

cpjpxq1n3#

最短的答案. PEP-3134说明了一切。raise Exception from e设置新异常的__cause__字段。
来自同一PEP的更长答案

  • __context__字段将被隐式地设置为except:块内的原始错误,除非被告知不与__suppress_context__ = True一起设置。
  • __cause__就像上下文一样,但必须使用from语法显式设置
  • 当你在except块中调用raise时,traceback总是会链接。你可以通过a)吞下一个异常except: pass或直接修改sys.exc_info()来摆脱回溯。
    冗长的答案
import traceback 
import sys

class CustomError(Exception):
    def __init__(self):
        super().__init__("custom")

def print_exception(func):
    print(f"\n\n\nEXECURTING FUNCTION '{func.__name__}' \n")
    try:
        func()
    except Exception as e:
        "Here is result of our actions:"
        print(f"\tException type:    '{type(e)}'")
        print(f"\tException message: '{e}'")
        print(f"\tException context: '{e.__context__}'")
        print(f"\tContext type:      '{type(e.__context__)}'")
        print(f"\tException cause:   '{e.__cause__}'")
        print(f"\tCause type:         '{type(e.__cause__)}'")
        print("\nTRACEBACKSTART>>>")
        traceback.print_exc()
        print("<<<TRACEBACKEND")

def original_error_emitter():
    x = {}
    print(x.does_not_exist)

def vanilla_catch_swallow():
    """Nothing is expected to happen"""
    try:
        original_error_emitter()
    except Exception as e:
        pass

def vanilla_catch_reraise():
    """Nothing is expected to happen"""
    try:
        original_error_emitter()
    except Exception as e:
        raise e

def catch_replace():
    """Nothing is expected to happen"""
    try:
        original_error_emitter()
    except Exception as e:
        raise CustomError()

def catch_replace_with_from():
    """Nothing is expected to happen"""
    try:
        original_error_emitter()
    except Exception as e:
        raise CustomError() from e

def catch_reset_trace():
    saw_an_error = False
    try:
        original_error_emitter()
    except Exception as e:
        saw_an_error = True
    if saw_an_error:
        raise CustomError()

print("Note: This will print nothing")
print_exception(vanilla_catch_swallow)
print("Note: This will print AttributeError and 1 stack trace")
print_exception(vanilla_catch_reraise)
print("Note: This will print CustomError with no context but 2 stack traces")
print_exception(catch_replace)
print("Note: This will print CustomError with AttributeError context and 2 stack traces")
print_exception(catch_replace_with_from)
print("Note: This will brake traceback chain")
print_exception(catch_reset_trace)

将导致以下输出:

Note: This will print nothing
EXECURTING FUNCTION 'vanilla_catch_swallow' 



Note: This will print AttributeError and 1 stack trace
EXECURTING FUNCTION 'vanilla_catch_reraise' 

        Exception type:    '<class 'AttributeError'>'
        Exception message: ''dict' object has no attribute 'does_not_exist''
        Exception context: 'None'
        Context type:      '<class 'NoneType'>'
        Exception cause:   'None'
        Cause type:         '<class 'NoneType'>'

TRACEBACKSTART>>>
Traceback (most recent call last):
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
    func()
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 41, in vanilla_catch_reraise
    raise e
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 39, in vanilla_catch_reraise
    original_error_emitter()
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 27, in original_error_emitter
    print(x.does_not_exist)
AttributeError: 'dict' object has no attribute 'does_not_exist'
<<<TRACEBACKEND


Note: This will print CustomError with no context but 2 stack traces
EXECURTING FUNCTION 'catch_replace' 

        Exception type:    '<class '__main__.CustomError'>'
        Exception message: 'custom'
        Exception context: ''dict' object has no attribute 'does_not_exist''
        Context type:      '<class 'AttributeError'>'
        Exception cause:   'None'
        Cause type:         '<class 'NoneType'>'

TRACEBACKSTART>>>
Traceback (most recent call last):
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 46, in catch_replace
    original_error_emitter()
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 27, in original_error_emitter
    print(x.does_not_exist)
AttributeError: 'dict' object has no attribute 'does_not_exist'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
    func()
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 48, in catch_replace
    raise CustomError()
CustomError: custom
<<<TRACEBACKEND


Note: This will print CustomError with AttributeError context and 2 stack traces
EXECURTING FUNCTION 'catch_replace_with_from' 

        Exception type:    '<class '__main__.CustomError'>'
        Exception message: 'custom'
        Exception context: ''dict' object has no attribute 'does_not_exist''
        Context type:      '<class 'AttributeError'>'
        Exception cause:   ''dict' object has no attribute 'does_not_exist''
        Cause type:         '<class 'AttributeError'>'

TRACEBACKSTART>>>
Traceback (most recent call last):
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 53, in catch_replace_with_from
    original_error_emitter()
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 27, in original_error_emitter
    print(x.does_not_exist)
AttributeError: 'dict' object has no attribute 'does_not_exist'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
    func()
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 55, in catch_replace_with_from
    raise CustomError() from e
CustomError: custom
<<<TRACEBACKEND


Note: This will brake traceback chain
EXECURTING FUNCTION 'catch_reset_trace' 

        Exception type:    '<class '__main__.CustomError'>'
        Exception message: 'custom'
        Exception context: 'None'
        Context type:      '<class 'NoneType'>'
        Exception cause:   'None'
        Cause type:         '<class 'NoneType'>'

TRACEBACKSTART>>>
Traceback (most recent call last):
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
    func()
  File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 64, in catch_reset_trace
    raise CustomError()
CustomError: custom
<<<TRACEBACKEND

相关问题