Python“raise from”用法

2025-03-18 08:54:00
admin
原创
49
摘要:问题描述:raisePython 中的和有什么区别raise from?try: raise ValueError except Exception as e: raise IndexError 得出Traceback (most recent call last): File "...

问题描述:

raisePython 中的和有什么区别raise 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

解决方案 1:

不同之处在于,当您使用 时from,会设置__cause__属性,并且消息指出异常直接由 引起。如果省略,from则不会__cause__设置,但也可能设置__context__属性,然后回溯会显示上下文,就像在处理期间发生了其他事情一样。

__context__如果您raise在异常处理程序中使用,则设置会发生;如果您raise在其他任何地方使用__context__,也不会设置。

如果__cause__设置了,则__suppress_context__ = True异常上也会设置一个标志;当__suppress_context__设置为时True__context__打印回溯时会忽略。

当从异常处理程序中引发您不想显示上下文(不想在处理另一个异常期间发生消息)时,请使用raise ... from None设置__suppress_context__True

换句话说,Python 在异常上设置了一个上下文,这样您就可以检查异常发生的位置,看看是否另一个异常被它替换了。您还可以为异常添加原因,使回溯明确说明另一个异常(使用不同的措辞),并且上下文被忽略(但在调试时仍然可以进行检查)。使用raise ... from None可以抑制正在打印的上下文。

参见raise声明文档:

from子句用于异常链:如果给出,第二个表达式必须是另一个异常类或实例,然后将其作为属性(可写)附加到引发的异常__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

另请参阅内置异常文档,了解有关异常的上下文和原因信息的详细信息。

解决方案 2:

2005 年,PEP 3134,异常链和嵌入式回溯引入了异常链:

  • 具有显式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)会导致ZeroDivisionError。该compute()函数捕获此异常并调用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__)

The above exception was the direct cause...'

elif exc.__context__:
    print_chain(exc.__context__)
    print '

During handling of the above exception, ...'

print_exc(exc)

2012 年,PEP 415《使用异常属性实现上下文抑制》raise EXCEPTION from None引入了使用显式(属性)实现异常上下文抑制的功能__suppress_context__

提议

将引入BaseException、 、的新属性。每当设置 时,将设置为。特别是,语法将设置为。异常打印代码将检查该属性以确定是否打印上下文和原因。将返回其原始目的和值。__suppress_context__`__cause____suppress_context__Trueraise exc from causeexc.__suppress_context__True__cause__`

具有__suppress_context__异常print_line_and_file属性的优先权。

总结一下,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 '
The above exception was the direct cause...'
    elif exc.__context__ and not exc.__suppress_context__:
        print_chain(exc.__context__)
        print '
During handling of the above exception, ...'
    print_exc(exc)

解决方案 3:

最简短的答案。PEP -3134说明了一切。raise Exception from e设置__cause__新异常的文件。

来自同一 PEP 的较长答案:

  • __context__字段将被隐式设置为块内的原始错误,except:除非被告知不要这样做__suppress_context__ = True

  • __cause__就像上下文一样,但必须使用from语法明确设置

  • traceback`raise在块内调用时将始终链接except。您可以通过以下方式摆脱回溯: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"


EXECURTING FUNCTION '{func.__name__}' 
")
    try:
        func()
    except Exception as e:
        "Here is result of our actions:"
        print(f"    Exception type:    '{type(e)}'")
        print(f"    Exception message: '{e}'")
        print(f"    Exception context: '{e.__context__}'")
        print(f"    Context type:      '{type(e.__context__)}'")
        print(f"    Exception cause:   '{e.__cause__}'")
        print(f"    Cause type:         '{type(e.__cause__)}'")
        print("
TRACEBACKSTART>>>")
        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

解决方案 4:

前言

文档中对此进行了技术解释。但它非常令人困惑,而且缺乏示例。以下是一些示例,我认为它们比__context____cause__、有趣的事情更清楚地解释了这种情况__supress_context__

主要例子

import sys
from traceback import print_exc

class ExceptionA(Exception): pass
class ExceptionB(Exception): pass

print('## Naked exception ##')
try:
    raise ExceptionA
except:
    print_exc(file=sys.stdout)

print('


## except and raise ##')
try:
    try:
        raise ExceptionA
    except ExceptionA:
        raise ExceptionB
except:
    print_exc(file=sys.stdout)

print('


## except and raise from e ##')
try:
    try:
        raise ExceptionA
    except ExceptionA as e:
        raise ExceptionB from e
except:
    print_exc(file=sys.stdout)

print('


## except and raise from None ##')
try:
    try:
        raise ExceptionA
    except ExceptionA:
        raise ExceptionB from None
except:
    print_exc(file=sys.stdout)

产量

## Naked exception ##
Traceback (most recent call last):
  File "...
aise.py", line 9, in <module>
    raise ExceptionA
ExceptionA



## except and raise ##
Traceback (most recent call last):
  File "...
aise.py", line 16, in <module>
    raise ExceptionA
ExceptionA

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "...
aise.py", line 18, in <module>
    raise ExceptionB
ExceptionB



## except and raise from e ##
Traceback (most recent call last):
  File "...
aise.py", line 25, in <module>
    raise ExceptionA
ExceptionA

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

Traceback (most recent call last):
  File "...
aise.py", line 27, in <module>
    raise ExceptionB from e
ExceptionB



## except and raise from None ##
Traceback (most recent call last):
  File "...
aise.py", line 36, in <module>
    raise ExceptionB from None
ExceptionB

因此我们得到了 4 种行为。

  • 除了“处理”之外,我们仅打印出ExceptionA

  • 当在处理异常 A 期间直接引发新的异常 B 时,我们会看到 A 和 B 的回溯,并由以下消息连接起来:“在处理上述异常期间,发生了另一个异常:”

  • 当在处理 A 的过程中从 A 引发新的异常 B 时,我们会看到 A 和 B 的回溯,并连接着以下消息“上述异常是导致以下异常的直接原因:”

  • 当在处理 A 的过程中从 None 引发新的异常 B 时,我们只能看到 B 的回溯。

为什么raise from e

我很难看出选项 2 和 3 之间的实际区别。唯一的区别是串联回溯之间的消息,因此区别纯粹与人类读者有关。因此两者具有不同的语义。据我所知,第二个选项 ( raise ExceptionB) 的语义表示:我们正在处理异常 A,然后发生了另一个意外异常 B。第三个选项 ( raise ExceptionB from e) 表示我们正在从内部异常转变ExceptionB为可能更具信息性的面向用户的异常。我认为一个常见的用例是这样的

from traceback import print_exc
import sys


my_dict = {'a': 1}

print('## No handling ##')
try:
    print(my_dict['b'])
except:
    print_exc(file=sys.stdout)

print('


## Handling the exception to add more info for the reader ##')
try:
    try:
        print(my_dict['b'])
    except KeyError as e:
        raise KeyError('You forgot to include a key in your dict!') from e
except:
    print_exc(file=sys.stdout)

产量

## No handling ##
Traceback (most recent call last):
  File "...
aise_2.py", line 9, in <module>
    print(my_dict['b'])
KeyError: 'b'



## Handling the exception to add more info for the reader ##
Traceback (most recent call last):
  File "...
aise_2.py", line 16, in <module>
    print(my_dict['b'])
KeyError: 'b'

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

Traceback (most recent call last):
  File "...
aise_2.py", line 18, in <module>
    raise KeyError('You forgot to include a key in your dict!') from e
KeyError: 'You forgot to include a key in your dict!'

这里的重点是,我们不希望回溯仅仅指出第二个键错误“发生在处理第一个键错误期间”。更有帮助的是指出第二个键错误发生是因为第一个键错误发生。也就是说,它们有相同的原因(因此__cause__)。因此,从这个意义上讲,开发人员可以向阅读回溯的人传达更多信息。但是,在某些情况下,您可能希望完全隐藏根本原因,并为用户提供更短、更具描述性的回溯。在这种情况下,您可以使用raise ... from None

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用