Python"从使用中提升"

dar*_*ine 154 python syntax exception-handling python-3.x

Python raiseraise fromPython 之间的区别是什么?

try:
    raise ValueError
except Exception as e:
    raise IndexError
Run Code Online (Sandbox Code Playgroud)

产量

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
Run Code Online (Sandbox Code Playgroud)

try:
    raise ValueError
except Exception as e:
    raise IndexError from e
Run Code Online (Sandbox Code Playgroud)

产量

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
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 180

不同之处在于,在使用时from,会设置__cause__属性并且消息指出异常是由直接引起的.如果省略则设置fromno __cause__,但也可以设置__context__属性,然后traceback将在处理其他事件时显示上下文.

__context__如果您raise在异常处理程序中使用,则设置发生; 如果你raise在其他地方使用过,也没有__context__设置.

如果__cause__设置了a,__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
Run Code Online (Sandbox Code Playgroud)

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

另请参阅内置异常文档,以获取有关上下文的详细信息以及附加到异常的原因信息.

  • 是否有任何理由使用`from`和`__cause__`明确地链接异常来代替隐含的`__context__`?有没有人会附加一个不同的例外情况而不是`除了`? (8认同)
  • @darkfeline:假设您的数据库API支持从各种来源打开数据库,包括Web和磁盘.如果打开数据库失败,您的API将*始终*引发`DatabaseError`.但是如果失败是由于文件无法打开导致的'IOError`或者由于URL无法正常工作而导致的`HTTPError`导致失败,那么这是您想要显式包含的上下文,因此使用API​​的开发人员可以调试为什么这是.在那一刻,你使用`来自original_exception的'raise DatabaseError'. (6认同)
  • @ laike9m:你的意思是当你处理异常`foo`时,想要引发一个*new*exception`bar`?然后你可以使用`foo`中的`raise bar'并让Python声明`foo`*直接导致`bar`*.如果你*不*使用`来自foo`,那么Python仍然会打印两者,但是在处理`foo`,`bar`被引发*时声明*,不同的消息,旨在标记错误处理中可能的错误. (4认同)
  • @darkfeline:如果开发人员在他们自己的API中包含数据库API的使用,并希望将'IOError`或`HTTPError`传递给*他们的*使用者,那么他们必须使用`来自databaseexception的提升NewException .__ cause__`,现在使用与他们刚捕获的`DatabaseException`不同的异常. (3认同)
  • @laike9m:完全正确。两者都是调试辅助工具,当然不是最终用户 UI。 (3认同)
  • Python 2 中是否有用于“raise .. from”构造的“from __future__”? (2认同)
  • @ dan3:不,没有.异常链接纯粹是Python 3的一项功能. (2认同)
  • @ArtOfWarfare:是的,您必须创建实例,手动设置 `__cause__` 属性,然后引发更新的异常。这在 2 和 3 中有效。但是,在 Python 3 中,如果您不删除 `e2` 变量,则最终可能会出现循环引用(因为 `raise` 设置的 `__traceback__` 属性将包含间接引用到框架,从而到“e2”名称,等等)。`six` 项目[创建一个使用 `try...finally` 的函数](https://bitbucket.org/gutworth/six/src/ca4580a5a648fc75abc568907e81abc80b05d58c/six.py?at=default&amp;fileviewer=file-view-default#六.py-715:733)。 (2认同)

Mag*_*ero 19

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

\n
    \n
  • 具有显式raise EXCEPTION或隐式 raise (__context__属性)的隐式链接;
  • \n
  • 与显式raise EXCEPTION from CAUSE(__cause__属性)的显式链接。
  • \n
\n
\n

动机

\n

在处理一个异常(异常A)的过程中,有可能发生另一异常(异常B)。在今天\xe2\x80\x99s的Python(版本2.4)中,如果发生这种情况,异常B会向外传播,异常A会丢失。为了调试问题,了解这两个异常很有用。该__context__属性自动保留此信息。

\n

有时,异常处理程序有意重新引发异常可能很有用,可以提供额外的信息,也可以将异常转换为另一种类型。该__cause__属性提供了一种显式的方式来记录异常的直接原因。

\n

[\xe2\x80\xa6]

\n

隐式异常链接

\n

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

\n
def compute(a, b):\n    try:\n        a/b\n    except Exception, exc:\n        log(exc)\n\ndef log(exc):\n    file = open(\'logfile.txt\')  # oops, forgot the \'w\'\n    print >>file, exc\n    file.close()\n
Run Code Online (Sandbox Code Playgroud)\n

调用compute(0, 0)会导致ZeroDivisionError. 该compute()函数捕获此异常并调用log(exc),但当该log()函数尝试写入未打开用于写入的\xe2\x80\x99t 文件时,也会引发异常。

\n

在今天的\xe2\x80\x99s Python 中,调用者compute()会抛出一个IOError. 丢失ZeroDivisionError了。通过建议的更改, 的实例IOError具有__context__保留ZeroDivisionError.

\n

[\xe2\x80\xa6]

\n

显式异常链接

\n

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

\n
raise EXCEPTION from CAUSE\n
Run Code Online (Sandbox Code Playgroud)\n

这相当于:

\n
exc = EXCEPTION\nexc.__cause__ = CAUSE\nraise exc\n
Run Code Online (Sandbox Code Playgroud)\n

在下面的示例中,数据库提供了几种不同类型存储的实现,其中文件存储是一种。数据库设计者希望错误作为DatabaseError对象传播,以便客户端不必了解特定于存储的详细信息,但又不想丢失底层错误信息。

\n
class DatabaseError(Exception):\n    pass\n\nclass FileDatabase(Database):\n    def __init__(self, filename):\n        try:\n            self.file = open(filename)\n        except IOError, exc:\n            raise DatabaseError(\'failed to open\') from exc\n
Run Code Online (Sandbox Code Playgroud)\n

如果对 的调用open()引发异常,则问题将报告为 a DatabaseError,并带有一个将 揭示为原始原因的__cause__属性。IOError

\n

增强报告功能

\n

默认异常处理程序将被修改以报告链式异常。__cause__通过遵循和属性来遍历异常链__context__,并__cause__优先考虑。为了与回溯的时间顺序保持一致,最近引发的异常显示在最后;也就是说,显示从最内层异常的描述开始,并将链备份到最外层异常。回溯的格式与往常一样,包含以下行之一:

\n
\n

上述异常是导致以下异常的直接原因:

\n
\n

或者

\n
\n

在处理上述异常的过程中,又出现了一个异常:

\n
\n

__cause__回溯之间,取决于它们是否由或分别链接__context__。以下是该过程的草图:

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

[\xe2\x80\xa6]

\n
\n

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

\n
\n

提议

\n

将引入BaseException,上的新属性。__suppress_context__每当__cause__设置时,__suppress_context__都会设置为True。特别是,raise exc from cause语法将设置exc.__suppress_context__True. 异常打印代码将检查该属性以确定是否打印上下文和原因。__cause__将回归其最初的目的和价值。

\n

__suppress_context__异常属性具有优先权print_line_and_file

\n

总而言之,raise exc from cause将相当于:

\n
exc.__cause__ = cause\nraise exc\n
Run Code Online (Sandbox Code Playgroud)\n

其中exc.__cause__ = cause隐式设置exc.__suppress_context__.

\n
\n

因此,在 PEP 415 中,PEP 3134 中给出的默认异常处理程序(其工作是报告异常)的过程概述如下:

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


y.s*_*hyk 5

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

来自同一个 PEP 的更长答案:

  • __context__字段将隐式设置为except:块内的原始错误,除非被告知不要使用__suppress_context__ = True
  • __cause__就像上下文一样,但必须使用from语法显式设置
  • tracebackraise当您在块内调用时将始终链接exceptexcept: pass您可以通过a)吞掉异常或直接搞乱来摆脱回溯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)
Run Code Online (Sandbox Code Playgroud)

将产生以下输出:

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
Run Code Online (Sandbox Code Playgroud)