34.异常处理

异常处理是 CPython 在操作无法正常完成时使用的控制流系统。它涵盖了明确的raise语句、失败的操作、失败的导入、失败的调用、生成器终止、上下文管理器清理、回溯构造以及通过帧的传播。

在源代码级别,异常如下所示:python id="cq34p8" try: value = risky() except ValueError: value = 0 在运行时,CPython 必须:```text id="o1a48o" execute the protected bytecode range detect failure record the active exception find a matching handler restore the frame stack to a valid state jump to handler bytecode run cleanup code propagate if no handler matches


## 34.1 什么是异常

异常是表示异常控制流的对象。

大多数例外是派生类的实例`BaseException````python id="m6c3g8"
raise ValueError("bad input")
```这将创建或使用异常对象并将执行转移出当前的正常路径。

异常层次结构开始于:```text id="axuv64"
BaseException
    SystemExit
    KeyboardInterrupt
    GeneratorExit
    Exception
        ValueError
        TypeError
        RuntimeError
        OSError
        ...
```大多数应用程序级异常源自`Exception`,不是直接来自`BaseException`

这很重要,因为:```python id="yj96ne"
except Exception:
    ...
```通常不会捕获`KeyboardInterrupt`, `SystemExit` 或者`GeneratorExit`

## 34.2 正常返回与异常返回

Python 函数可以通过两种主要方式退出:```text id="ea7eqq"
normal return
exception propagation
```正常返回:```python id="0q9s27"
def f():
    return 42
```从概念上讲:```text id="g7wklr"
LOAD_CONST 42
RETURN_VALUE
```异常退出:```python id="h98nnl"
def f():
    raise ValueError("bad")
```从概念上讲:```text id="zpp0ri"
create ValueError object
set exception state
unwind frame
```呼叫者收到:```text id="szzwfe"
a returned PyObject pointer
or an error indication with exception state set
``` C API 级别,许多函数都遵循以下形式:```c id="b99eij"
PyObject *result = some_operation();
if (result == NULL) {
    /* exception is set */
    return NULL;
}
````NULL`返回信号失败。实际的异常存储在解释器线程状态中。

## 34.3 异常作为控制流

异常用于错误,但也用于结构化控制流。

示例:```text id="kfz3yw"
StopIteration ends iteration
StopAsyncIteration ends async iteration
GeneratorExit closes generators
KeyboardInterrupt interrupts execution
SystemExit requests interpreter exit
ImportError reports import failure
AttributeError reports missing attributes
````for`循环取决于`StopIteration`内部:```python id="79e3jp"
for x in xs:
    body(x)
```从概念上讲:```text id="9mx6jc"
iterator = iter(xs)

while true:
    try:
        x = next(iterator)
    except StopIteration:
        break
    body(x)
```所以异常是正常解释器协议的一部分。

## 34.4 引发异常

一个`raise`语句将控制权转移到异常机制。```python id="ctu8dt"
raise ValueError("bad")
```字节码大致执行:```text id="h4k87k"
load ValueError
load "bad"
call ValueError("bad")
raise resulting exception
```加薪可以使用:```python id="zewoqj"
raise SomeError
raise SomeError("message")
raise existing_exception
raise
```一个裸露的`raise`仅在处理活动异常时有效:```python id="3qrtpf"
try:
    risky()
except ValueError:
    raise
```它重新引发当前处理的异常。

## 34.5 异常类和实例

Python 允许您引发异常类或异常实例。```python id="q6npyl"
raise ValueError
```CPython 在需要时将其标准化为实例。```python id="z91m3v"
raise ValueError("bad")
```已经提供了一个实例。

在内部,异常处理通常需要规范化的三重或等效状态:```text id="dk5r5k"
exception type
exception value
traceback
```现代 CPython 通过内部异常结构来表示和管理这种状态,但概念模型仍然有用。

## 34.6 回溯

回溯记录异常发生的位置。

例子:```python id="1mo1jg"
def a():
    b()

def b():
    c()

def c():
    1 / 0

a()
```回溯包含活动帧的条目:```text id="83c801"
a
b
c
ZeroDivisionError
```每个回溯条目指的是:```text id="qjfi2r"
frame
code object
instruction position
source line information
next traceback entry
```回溯是结构化的运行时数据,而不仅仅是格式化的文本。

这就是异常可以间接保留局部变量的原因:```text id="yoeiyb"
exception
    traceback
        frame
            locals
```## 34.7 框架展开

当当前帧中未处理异常时,CPython 会展开该帧并将异常传播给调用者。

例子:```python id="s11efk"
def f():
    raise ValueError

def g():
    f()

def h():
    g()
```如果不存在处理程序:```text id="eeh2v4"
frame f raises
frame f unwinds
frame g receives exception
frame g unwinds
frame h receives exception
frame h unwinds
top level prints traceback
```如果处理程序存在于`g`,传播在那里停止:```python id="5x9m87"
def g():
    try:
        f()
    except ValueError:
        return 0
```异常处理程序成为新的控制流目标。

## 34.8 异常表

现代 CPython 使用与代码对象关联的异常表来描述受保护区域和处理程序。

一个`try`陈述:```python id="pl5k0d"
try:
    risky()
except ValueError:
    recover()
```编译为:```text id="p4ajhn"
bytecode for risky()
bytecode for handler
exception table mapping protected range to handler
```异常表记录的信息如下:```text id="ca8e5f"
protected bytecode start
protected bytecode end
handler target
stack depth to restore
handler kind
```当异常发生时,解释器使用当前指令位置在表中搜索处理程序。

这避免了为正常执行维护一些较旧的块堆栈机制,并让零成本异常区域避免了没有异常发生时的开销。

## 34.9 堆栈恢复

当临时值位于帧堆栈上时可能会发生异常。

例子:```python id="iz2gr7"
x = f(g(), h())
```如果`h()`引发,堆栈可能包含:```text id="q5o3qr"
f
result_of_g
```致电给`f`永远不会发生。 CPython 必须释放临时引用并将堆栈恢复到异常处理程序期望的深度。

异常表元数据告诉解释器在进入处理程序之前要恢复的堆栈深度。

这对于正确性至关重要。处理程序必须从已知的堆栈形状开始。

## 34.10 匹配异常

`except`子句检查活动异常是否与类型或类型元组匹配。```python id="ofe6wa"
try:
    risky()
except ValueError:
    handle()
```如果异常是一个实例,则处理程序匹配`ValueError`或子类。

元组匹配:```python id="3jtudw"
except (ValueError, TypeError):
    handle()
```匹配任一类型。

匹配操作使用异常类关系。这不是字符串比较。

## 34.11 处理程序订单

处理程序按源顺序进行测试。```python id="dxve0m"
try:
    risky()
except Exception:
    handle_general()
except ValueError:
    handle_value()
````ValueError`处理程序无法访问,因为`ValueError`是一个子类`Exception`

正确的排序将特定的处理程序放在第一位:```python id="6s99w7"
try:
    risky()
except ValueError:
    handle_value()
except Exception:
    handle_general()
```编译器通常不会拒绝无法访问的异​​常处理程序。运行时间遵循给定的顺序。

## 34.12 绑定异常变量

`except ... as name`子句绑定异常对象。```python id="gn531x"
try:
    risky()
except ValueError as exc:
    print(exc)
```处理程序内部:```text id="1csxmt"
exc -> exception instance
```在处理程序之后,Python 清除此绑定以减少涉及回溯的引用循环。

从概念上讲:```python id="lqlby5"
except ValueError as exc:
    ...
finally:
    del exc
```这可以防止常见的保留周期:```text id="bi9brc"
exception
    traceback
        frame
            locals
                exception
```## 34.13 裸露`except`一个裸露的处理程序几乎可以捕获所有内容:```python id="cfaisv"
try:
    risky()
except:
    handle()
```它捕获源自以下的异常`BaseException` 包括`KeyboardInterrupt``SystemExit`

这通常太宽泛了。

更喜欢:```python id="8l0yll"
except Exception:
    handle()
```处理普通应用程序错误时。

一个裸露的`except`当代码必须执行清理然后重新引发时可能仍然合适:```python id="2sx6wj"
try:
    risky()
except:
    cleanup()
    raise
```## 34.14`else`积木

一个`try`声明可以有一个`else`堵塞。```python id="ne9sbh"
try:
    value = parse()
except ValueError:
    value = default
else:
    use(value)
````else`块仅在以下情况下运行`try`块已无异常地完成。

如果出现以下情况,则不会运行:```text id="tmvff1"
the try block raises
the try block returns
the try block breaks
the try block continues
```在字节码级别,这是处理程序块周围的正常分支控制流。

## 34.15`finally`积木

一个`finally`当控制离开时块运行`try`堵塞。```python id="sk93di"
try:
    risky()
finally:
    cleanup()
```清理运行时间为:```text id="6kjx4b"
normal fallthrough
return
exception
break
continue
```例子:```python id="pkbx1h"
def f():
    try:
        return 1
    finally:
        print("cleanup")
```退货正在等待中`finally`身体奔跑。

如果`finally`body 引发,它替换待处理的返回:```python id="a8odfm"
def f():
    try:
        return 1
    finally:
        raise RuntimeError("cleanup failed")
```这个函数引发`RuntimeError`而不是返回`1`

## 34.16`return`里面`finally`一个`return`里面`finally`覆盖之前的异常或返回。```python id="lip58q"
def f():
    try:
        raise ValueError("bad")
    finally:
        return 10
```这返回`10`。这`ValueError`被压制。

这种行为合法但危险。它可以隐藏错误。

在运行时,`finally`块控制最终的退出路径。如果它返回,则该返回将成为函数的结果。

## 34.17 上下文管理器和异常

一个`with`语句建立在异常处理的基础上。```python id="0710p4"
with manager as value:
    body(value)
```从概念上讲:```python id="nd0spg"
mgr = manager
exit = mgr.__exit__
value = mgr.__enter__()
try:
    body(value)
except BaseException as exc:
    suppress = exit(type(exc), exc, exc.__traceback__)
    if not suppress:
        raise
else:
    exit(None, None, None)
```如果`__exit__`返回真值,异常被抑制。

这就是上下文管理器如何实现事务回滚、文件关闭、锁定、临时状态和资源清理。

## 34.18`with`字节码

一个`with`语句编译成字节码:```text id="x3hc42"
loads the context manager
calls __enter__
stores the as-target
executes the body
calls __exit__ on normal exit
calls __exit__ on exceptional exit
suppresses or reraises based on return value
```解释器必须保持足够的状态来调用`__exit__`即使身体抬起。

这使得`with`的结构化形式`try/finally`plus exception suppression.

## 34.19 Chained Exceptions

Python records exception context.

例子:```python id="vpsoic"
try:
    int("x")
except ValueError:
    raise RuntimeError("parse failed")
````RuntimeError`有指向原始内容的上下文`ValueError`

回溯输出显示如下内容:```text id="9g0agc"
During handling of the above exception, another exception occurred
```显式链接使用`from`:

```python id="iw2vuv"
raise RuntimeError("parse failed") from exc
```抑制上下文使用:```python id="gg994p"
raise RuntimeError("parse failed") from None
```异常对象具有以下字段:```text id="pqfm3s"
__context__
__cause__
__suppress_context__
__traceback__
```## 34.20 线程状态中的异常状态

活动异常存储在解释器线程状态中。

这就是为什么 C 函数可以通过返回来表示失败`NULL`同时将异常留在其他地方。

从概念上讲:```text id="y794el"
thread state
    current exception
    handled exception stack
``` C 助手失败时:```c id="gctod1"
PyErr_SetString(PyExc_ValueError, "bad");
return NULL;
```调用者检查返回值并知道设置了异常。

然后评估循环传播或处理它。

## 34.21 C API 错误约定

CPython C API 函数通常遵循几种错误约定之一。

指针返回函数:```c id="f6xl1n"
PyObject *obj = PyLong_FromString(text, NULL, 10);
if (obj == NULL) {
    return NULL;
}
```返回整数的函数:```c id="tj37v0"
int rc = PyObject_SetAttrString(obj, "x", value);
if (rc < 0) {
    return NULL;
}
```类似布尔值的检查可能会返回`1`, `0` 或者`-1`:

```c id="71hl88"
int ok = PyObject_IsTrue(obj);
if (ok < 0) {
    return NULL;
}
```错误结果和异常状态必须一致。返回错误代码而不设置异常通常是一个错误。

## 34.22 从 C 开始

C 代码通过设置错误状态引发 Python 异常。

例子:```c id="c8dwxx"
PyErr_SetString(PyExc_TypeError, "expected integer");
return NULL;
```对于格式化消息:```c id="npt9uu"
PyErr_Format(PyExc_ValueError, "bad value: %d", value);
return NULL;
```为了传播现有异常,C 代码返回错误标记而不覆盖异常。

这种模式允许错误在许多 C 函数中移动,直到计算循环找到 Python 处理程序或退出到顶层。

## 34.23 清除异常

有时,C 代码会有意处理异常并将其清除。

从概念上讲:```c id="44mmje"
PyObject *value = PyObject_GetAttrString(obj, "optional");
if (value == NULL) {
    if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
        PyErr_Clear();
        value = default_value;
    }
    else {
        return NULL;
    }
}
```清除错误的异常可以隐藏真正的错误。

Python 代码具有类似的模式:```python id="rcjz53"
try:
    value = obj.optional
except AttributeError:
    value = default
```关键规则是仅捕获您打算处理的异常。

## 34.24 异常处理过程中的异常

处理程序可以引发另一个异常。```python id="gc39fq"
try:
    risky()
except ValueError:
    recover_badly()
```如果`recover_badly()`引发时,新异常会替换处理程序的正常结果,同时保留有关原始异常的上下文。

一个`finally`块也可以在清理过程中升高。

CPython 必须保留足够的状态来构建有用的回溯链。

## 34.25 异常和生成器

生成器使用异常进行控制。

生成器通过提高结束`StopIteration`内部调用者。```python id="dz6ljh"
def gen():
    yield 1

g = gen()
next(g)
next(g)
```第二个`next(g)`提高`StopIteration`

一个`return value`生成器内部成为附加到的值`StopIteration````python id="r2gskh"
def gen():
    return 42
    yield

g = gen()
try:
    next(g)
except StopIteration as exc:
    print(exc.value)
```输出是:```text id="4y674v"
42
```生成器最终确定用途`GeneratorExit`

## 34.26 PEP 479 和发电机

一个`StopIteration`意外地在发电机体内升起,转变为`RuntimeError`

例子:```python id="muj1jf"
def gen():
    raise StopIteration
    yield
```这可以避免意外发生时出现的细微错误`StopIteration`默默地终止生成器。

生成器协议仍然使用`StopIteration`在边界处。该转换适用于生成器执行内部。

## 34.27 异常和协程

协程还使用异常路径来进行取消和失败。

等待的协程可以正常完成:```text id="tp92nc"
return value
```或失败:```text id="cvojm0"
raise exception
```取消通常由注入到协程执行中的异常来表示。

协程帧恢复时出现异常而不是正常值。

这意味着异步框架深度依赖 CPython 的框架挂起、恢复和异常传播模型。

## 34.28 异常组

现代 Python 包含用于一起表示多个异常的异常组。```python id="0cenmj"
raise ExceptionGroup("many", [ValueError("a"), TypeError("b")])
```它们的处理方式是`except*`:

```python id="ihrw3w"
try:
    raise ExceptionGroup("many", [ValueError("a"), TypeError("b")])
except* ValueError as group:
    handle_values(group)
except* TypeError as group:
    handle_types(group)
```这对于多个任务可能同时失败的并发程序很重要。

解释器必须拆分并匹配异常组`except*`处理程序。

## 34.29`except*`

`except*`与普通不同`except`

普通的`except`为一个活动异常选择一个处理程序。`except*`可以拆分异常组并将不同的子异常路由到不同的处理程序。

从概念上讲:```text id="7m7ae6"
ExceptionGroup(ValueError, TypeError)
    except* ValueError receives ValueError subgroup
    except* TypeError receives TypeError subgroup
```任何不匹配的子群都会继续传播。

这增加了结构化的多错误处理,而不会丢弃单个异常标识。

## 34.30 回溯保留

回溯使帧保持活动状态。

例子:```python id="7vw5yb"
saved = None

def f():
    big = bytearray(100_000_000)
    raise RuntimeError

try:
    f()
except RuntimeError as exc:
    saved = exc
```保存的异常可能会保留其回溯。回溯保留框架。框架保留局部变量`big`

保留链:```text id="9fqt5r"
saved exception
    traceback
        frame
            locals
                big bytearray
```这就是为什么长期异常可以保留大量内存。

## 34.31 清理回溯

您可以通过不存储异常超过需要的时间或清除回溯引用来避免保留。```python id="tb2iqr"
try:
    f()
except RuntimeError as exc:
    handle(exc)
    exc = None
```对于显式清理:```python id="smrtfq"
exc.__traceback__ = None
```小心使用它,因为回溯对于调试很有用。

一般规则是:存储异常存储上下文。

## 34.32`finally`和参考清理`finally`常用于释放资源。```python id="iibrsm"
resource = acquire()
try:
    use(resource)
finally:
    resource.close()
```资源管理的首选形式通常是上下文管理器:```python id="tfg64f"
with acquire() as resource:
    use(resource)
```两种形式都依赖于异常处理机制,以确保在控制离开受保护块时执行清理操作。

## 34.33 例外情况和`__del__`对象终结器中引发的异常经过特殊处理。```python id="6vbsh5"
class C:
    def __del__(self):
        raise RuntimeError("bad finalizer")
```例外情况来自`__del__`在发生垃圾收集时无法正常传播到用户代码。 CPython 通过不可引发的异常机制来报告它。

这就是为什么终结器应该避免引发。

## 34.34 不可引发的异常

在无法正常传播的情况下会发生一些例外情况。

示例:```text id="ydy6r1"
__del__ finalizers
weakref callbacks
some cleanup hooks
background finalization contexts
```CPython 通过不可引发的异常处理来报告这些,可通过`sys.unraisablehook`

这可以保留诊断信息,而不会破坏不可能的控制流上下文。

## 34.35 例外和导入

导入失败使用异常。```python id="54d93q"
import missing_module
```提高`ModuleNotFoundError`

导入可能会在几个阶段失败:```text id="0yjm9i"
module search
loader creation
source reading
bytecode loading
module execution
submodule import
package initialization
```如果模块执行引发,则导入会失败并出现该异常。

由于导入模块会运行其顶级代码,因此导入期间可能会出现任意异常。

## 34.36 异常和属性查找

缺少属性查找引发`AttributeError````python id="qcd62q"
obj.missing
```但自定义查找可以引发任何问题:```python id="otwoa7"
class C:
    @property
    def x(self):
        raise RuntimeError("failed")
```这对于`getattr`, `hasattr`和动态框架。`hasattr`捕获物`AttributeError`,不是任意的例外。

## 34.37 异常和迭代

迭代使用`StopIteration`

手动等效:```python id="0pw412"
it = iter(xs)

while True:
    try:
        x = next(it)
    except StopIteration:
        break
    body(x)
```在字节码级别,循环指令识别迭代器耗尽并分支出循环。

这是正常的、预期的异常路径。

## 34.38 异常和模式匹配

模式匹配可以在内部使用失败,而不会暴露普通不匹配的异常。```python id="vlqdr7"
match value:
    case {"x": x}:
        ...
    case _:
        ...
```匹配引擎必须区分:```text id="b58239"
pattern does not match
operation raises a real exception
```失败的模式应该转移到下一个案例。真正的异常应该传播。

## 34.39 异常和字节码指令

许多字节码指令都可以引发。

示例:

|说明书种类|可能出现的故障|
|---|---|
|`LOAD_GLOBAL` | `NameError` |
| `LOAD_ATTR` | `AttributeError`或任意描述符错误 |
|`BINARY_OP` | `TypeError`, `ZeroDivisionError`, 用户异常 |
|`CALL`|参数错误或被调用者异常 |
|`IMPORT_NAME` | `ImportError`, 任意模块执行错误 |
|`FOR_ITER`|正常耗尽之外的迭代器异常 |
|`STORE_ATTR` | `AttributeError`, 描述符错误 |
|`BUILD_LIST`|内存分配失败 |

评估循环必须假设大多数指令可能会失败。

## 34.40 求值循环中的异常安全

每条指令的实现都需要一个错误路径。

简化:```c id="b2kgao"
PyObject *result = operation();
if (result == NULL) {
    goto error;
}
push(result);
```错误路径必须:```text id="j5xxqd"
preserve the active exception
release temporary references
restore stack state
find a handler or unwind
update traceback information
avoid clobbering unrelated exception state
```这是解释器实现中最难的部分之一。

## 34.41 异常和引用计数

异常是 Python 对象,因此它们是引用计数的。

回溯、框架和异常对象可以形成循环:```text id="olvwqs"
exception
    traceback
        frame
            locals
                exception
```CPython 对异常变量有特殊的清理行为,以减少这些周期。循环垃圾收集器还可以收集不可达的循环。

尽管如此,存储异常可以延长对象的生命周期。

## 34.42 异常规范化

在内部,CPython 通常需要规范化异常,以便它有一个具体的异常实例。

输入形式:```python id="dzbie3"
raise ValueError
raise ValueError("bad")
```两者都成为异常类型和实例。

规范化确保以后的代码可以检查:```text id="zqj7jd"
exception class
exception instance
traceback
cause
context
notes
```如果构造异常实例失败,异常规范化本身可能会失败。

## 34.43 异常注释

Python 异常可以带有注释。```python id="j1c2ow"
try:
    raise ValueError("bad")
except ValueError as exc:
    exc.add_note("while parsing config")
    raise
```注释在回溯输出中提供附加诊断文本。

它们存储在异常对象上并且不会更改匹配行为。

## 34.44 语法错误

语法错误也是例外,但它们发生在正常字节码执行之前。```python id="7eglix"
eval("if")
```提高`SyntaxError`

编译阶段的异常包括:```text id="11480v"
SyntaxError
IndentationError
TabError
```这些发生在解析或编译期间,在评估循环执行结果代码之前。

## 34.45 内存错误

分配失败引发`MemoryError` CPython 可以报告它时。

可以分配的示例操作:```text id="u0gk53"
creating objects
building lists
creating strings
expanding dictionaries
constructing tracebacks
formatting exception messages
```异常处理本身可能会分配内存,因此运行时在报告内存不足情况时必须小心。

## 34.46 键盘中断和信号`KeyboardInterrupt`通常在解释器处理中断信号(例如 Ctrl-C)时引发。

该信号不由任意机器指令处理。 CPython 记录待处理信号状态并检查评估循环中的安全点。

处理时,解释器提出`KeyboardInterrupt`在执行线程中。

这是服务于外部控制流的异常机制的另一个例子。

## 34.47 系统退出`sys.exit()`提高`SystemExit`。```python id="qyxowo"
import sys
sys.exit(1)
```如果未捕获,解释器将以给定状态退出。

因为是异常,所以可以捕获:```python id="yl2p70"
try:
    sys.exit(1)
except SystemExit:
    print("caught")
```这就是为什么`SystemExit`直接源自`BaseException`,如此广泛`except Exception`处理程序通常不会抑制进程退出。

## 34.48 常见误解

|误会 |正确型号 |
|---|---|
|例外仅适用于错误 |他们还实现迭代、退出、取消和控制协议
|回溯只是文本 |它是一个帧记录链|
|`except Exception`抓住一切|它并没有抓住所有`BaseException`子类 |
|`finally`始终保留原始错误 |回报或加薪`finally`可以代替吗|
|`with`只调用`close`|它调用`__enter__``__exit__`,例外详细信息 |
|`hasattr`无副作用 |它执行属性查找并可以运行用户代码 |
| C代码直接返回异常对象 |通常它会设置异常状态并返回错误哨兵 |
|存储异常是无害的 |它可以保留回溯、框架和大型局部变量 |

## 34.49 阅读策略

要研究异常处理,请拆解小示例。

从以下开始:```python id="d0qfxt"
def f(x):
    try:
        return 10 / x
    except ZeroDivisionError:
        return 0
```然后检查:```python id="lefgod"
import dis
dis.dis(f)
```还可以检查异常表输出(如果可用):```python id="mj1mrk"
dis.show_code(f)
```然后测试:```python id="fo0fk2"
try/finally
try/except/else
with statements
raise from
bare raise
generators with return
ExceptionGroup and except*
```对于每种情况,跟踪:```text id="pag3xt"
where the protected range begins
where the handler begins
what stack cleanup is required
which exception is active
whether the frame returns or unwinds
what traceback is retained
```## 34.50 章总结

CPython 中的异常处理是一个结构化的控制流系统。它使用异常对象、线程状态异常存储、帧展开、回溯构造、异常表、堆栈恢复、处理程序匹配和清理块。

核心模型是:```text id="huu536"
operation fails
    
exception state is set
    
current frame looks for a handler
    
handler found: restore stack and jump
    
no handler: unwind frame and propagate
    
top level: print traceback or terminate
```异常连接运行时的许多部分:字节码执行、函数调用、上下文管理器、生成器、协程、导入、属性查找、C API 错误约定、信号处理和内存管理。

了解异常意味着了解错误处理和 Python 控制流机制的主要部分。