34. Xử lý ngoại lệ
34. Xử lý ngoại lệ
Xử lý ngoại lệ là hệ thống luồng điều khiển mà CPython sử dụng khi một thao tác không thể hoàn thành bình thường. Nó bao gồm rõ ràngraisecâu lệnh, hoạt động không thành công, nhập không thành công, cuộc gọi không thành công, chấm dứt trình tạo, dọn dẹp trình quản lý bối cảnh, xây dựng truy nguyên và truyền bá qua khung.
Ở cấp độ nguồn, các ngoại lệ trông như thế này:python id="cq34p8" try: value = risky() except ValueError: value = 0 Khi chạy, CPython phải:```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 Ngoại lệ là gì
Ngoại lệ là một đối tượng đại diện cho luồng điều khiển bất thường.
Hầu hết các trường hợp ngoại lệ là các thể hiện của các lớp bắt nguồn từ`BaseException`.
```python id="m6c3g8"
raise ValueError("bad input")
```Điều này tạo ra hoặc sử dụng một đối tượng ngoại lệ và chuyển việc thực thi ra khỏi đường dẫn thông thường hiện tại.
Hệ thống phân cấp ngoại lệ bắt đầu bằng:```text id="axuv64"
BaseException
SystemExit
KeyboardInterrupt
GeneratorExit
Exception
ValueError
TypeError
RuntimeError
OSError
...
```Hầu hết các trường hợp ngoại lệ ở cấp độ ứng dụng đều xuất phát từ`Exception`, không trực tiếp từ`BaseException`.
Điều này quan trọng bởi vì:```python id="yj96ne"
except Exception:
...
```thường không bắt được`KeyboardInterrupt`, `SystemExit`, hoặc`GeneratorExit`.
## 34.2 Trả về bình thường và Trả về ngoại lệ
Một hàm Python có thể thoát theo hai cách chính:```text id="ea7eqq"
normal return
exception propagation
```Lợi nhuận bình thường:```python id="0q9s27"
def f():
return 42
```Về mặt khái niệm:```text id="g7wklr"
LOAD_CONST 42
RETURN_VALUE
```Thoát ngoại lệ:```python id="h98nnl"
def f():
raise ValueError("bad")
```Về mặt khái niệm:```text id="zpp0ri"
create ValueError object
set exception state
unwind frame
```Người gọi sẽ nhận được:```text id="szzwfe"
a returned PyObject pointer
or an error indication with exception state set
```Ở cấp độ API C, nhiều hàm tuân theo hình dạng này:```c id="b99eij"
PyObject *result = some_operation();
if (result == NULL) {
/* exception is set */
return NULL;
}
```các`NULL`lỗi tín hiệu trả về. Ngoại lệ thực tế được lưu trữ ở trạng thái luồng trình thông dịch.
## 34.3 Ngoại lệ dưới dạng luồng điều khiển
Các ngoại lệ được sử dụng cho các lỗi cũng như cho luồng điều khiển có cấu trúc.
Ví dụ:```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
```các`for`vòng lặp phụ thuộc vào`StopIteration`nội bộ:```python id="79e3jp"
for x in xs:
body(x)
```Về mặt khái niệm:```text id="9mx6jc"
iterator = iter(xs)
while true:
try:
x = next(iterator)
except StopIteration:
break
body(x)
```Vì vậy, các ngoại lệ là một phần của giao thức thông dịch thông thường.
## 34.4 Đưa ra một ngoại lệ
A`raise`câu lệnh chuyển quyền kiểm soát tới bộ máy ngoại lệ.```python id="ctu8dt"
raise ValueError("bad")
```Mã byte gần như thực hiện:```text id="h4k87k"
load ValueError
load "bad"
call ValueError("bad")
raise resulting exception
```Việc tăng lương có thể sử dụng:```python id="zewoqj"
raise SomeError
raise SomeError("message")
raise existing_exception
raise
```Một trần`raise`chỉ hợp lệ khi xử lý một ngoại lệ đang hoạt động:```python id="3qrtpf"
try:
risky()
except ValueError:
raise
```Nó làm tăng lại ngoại lệ hiện đang được xử lý.
## 34.5 Các lớp và trường hợp ngoại lệ
Python cho phép bạn đưa ra một lớp ngoại lệ hoặc một thể hiện ngoại lệ.```python id="q6npyl"
raise ValueError
```CPython bình thường hóa điều này thành một phiên bản khi cần thiết.```python id="z91m3v"
raise ValueError("bad")
```đã cung cấp một ví dụ.
Trong nội bộ, việc xử lý ngoại lệ thường cần trạng thái ba hoặc tương đương được chuẩn hóa:```text id="dk5r5k"
exception type
exception value
traceback
```CPython hiện đại thể hiện và quản lý trạng thái này thông qua các cấu trúc ngoại lệ bên trong, nhưng mô hình khái niệm vẫn hữu ích.
## 34.6 Truy nguyên
Truy nguyên ghi lại nơi ngoại lệ đã di chuyển.
Ví dụ:```python id="1mo1jg"
def a():
b()
def b():
c()
def c():
1 / 0
a()
```Truy nguyên chứa các mục nhập cho các khung hoạt động:```text id="83c801"
a
b
c
ZeroDivisionError
```Mỗi mục truy nguyên đề cập đến:```text id="qjfi2r"
frame
code object
instruction position
source line information
next traceback entry
```Truy nguyên là dữ liệu thời gian chạy có cấu trúc, không chỉ là văn bản được định dạng.
Đây là lý do tại sao các ngoại lệ có thể giữ lại các biến cục bộ một cách gián tiếp:```text id="yoeiyb"
exception
traceback
frame
locals
```## 34.7 Giải phóng khung hình
Khi một ngoại lệ không được xử lý trong khung hiện tại, CPython sẽ mở khung và truyền ngoại lệ đó cho người gọi.
Ví dụ:```python id="s11efk"
def f():
raise ValueError
def g():
f()
def h():
g()
```Nếu không có trình xử lý nào tồn tại:```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
```Nếu một trình xử lý tồn tại trong`g`, sự lan truyền dừng lại ở đó:```python id="5x9m87"
def g():
try:
f()
except ValueError:
return 0
```Trình xử lý ngoại lệ trở thành mục tiêu luồng điều khiển mới.
## 34.8 Bảng ngoại lệ
CPython hiện đại sử dụng các bảng ngoại lệ được liên kết với các đối tượng mã để mô tả các vùng và trình xử lý được bảo vệ.
MỘT`try`tuyên bố:```python id="pl5k0d"
try:
risky()
except ValueError:
recover()
```biên dịch thành:```text id="p4ajhn"
bytecode for risky()
bytecode for handler
exception table mapping protected range to handler
```Bảng ngoại lệ ghi lại các thông tin như:```text id="ca8e5f"
protected bytecode start
protected bytecode end
handler target
stack depth to restore
handler kind
```Khi một ngoại lệ xảy ra, trình thông dịch sử dụng vị trí lệnh hiện tại để tìm kiếm trình xử lý trong bảng.
Điều này tránh việc duy trì một số máy móc ngăn xếp khối cũ hơn để thực thi bình thường và cho phép các vùng ngoại lệ có chi phí bằng 0 tránh được chi phí chung khi không có ngoại lệ nào xảy ra.
## 34.9 Khôi phục ngăn xếp
Một ngoại lệ có thể xảy ra khi các giá trị tạm thời nằm trên ngăn xếp khung.
Ví dụ:```python id="iz2gr7"
x = f(g(), h())
```Nếu như`h()`tăng lên, ngăn xếp có thể chứa:```text id="q5o3qr"
f
result_of_g
```Cuộc gọi đến`f`không bao giờ xảy ra. CPython phải giải phóng các tham chiếu tạm thời và khôi phục ngăn xếp về độ sâu mà trình xử lý ngoại lệ mong đợi.
Siêu dữ liệu bảng ngoại lệ cho trình thông dịch biết độ sâu ngăn xếp cần khôi phục trước khi nhập trình xử lý.
Điều này là cần thiết cho sự đúng đắn. Trình xử lý phải bắt đầu với hình dạng ngăn xếp đã biết.
## 34.10 So khớp một ngoại lệ
Một`except`mệnh đề kiểm tra xem ngoại lệ hoạt động có khớp với một loại hoặc bộ loại hay không.```python id="ofe6wa"
try:
risky()
except ValueError:
handle()
```Trình xử lý khớp nếu ngoại lệ là một thể hiện của`ValueError`hoặc một lớp con.
So khớp bộ dữ liệu:```python id="3jtudw"
except (ValueError, TypeError):
handle()
```phù hợp với một trong hai loại.
Hoạt động so khớp sử dụng các mối quan hệ lớp ngoại lệ. Nó không phải là một so sánh chuỗi.
## 34.11 Lệnh xử lý
Trình xử lý được kiểm tra theo thứ tự nguồn.```python id="dxve0m"
try:
risky()
except Exception:
handle_general()
except ValueError:
handle_value()
```các`ValueError`trình xử lý không thể truy cập được vì`ValueError`là một lớp con của`Exception`.
Thứ tự đúng đặt trình xử lý cụ thể lên hàng đầu:```python id="6s99w7"
try:
risky()
except ValueError:
handle_value()
except Exception:
handle_general()
```Trình biên dịch thường không từ chối các trình xử lý ngoại lệ không thể truy cập được. Thời gian chạy tuân theo thứ tự đã cho.
## 34.12 Ràng buộc biến ngoại lệ
Một`except ... as name`mệnh đề liên kết đối tượng ngoại lệ.```python id="gn531x"
try:
risky()
except ValueError as exc:
print(exc)
```Bên trong trình xử lý:```text id="1csxmt"
exc -> exception instance
```Sau trình xử lý, Python xóa ràng buộc này để giảm các chu kỳ tham chiếu liên quan đến truy nguyên.
Về mặt khái niệm:```python id="lqlby5"
except ValueError as exc:
...
finally:
del exc
```Điều này ngăn cản một chu kỳ lưu giữ chung:```text id="bi9brc"
exception
traceback
frame
locals
exception
```## 34.13 Trần`except`Một người xử lý trần sẽ nắm bắt được hầu hết mọi thứ:```python id="cfaisv"
try:
risky()
except:
handle()
```Nó bắt các ngoại lệ bắt nguồn từ`BaseException`, bao gồm`KeyboardInterrupt`Và`SystemExit`.
Điều này thường quá rộng.
Thích hơn:```python id="8l0yll"
except Exception:
handle()
```khi xử lý các lỗi ứng dụng thông thường.
Một trần`except`vẫn có thể phù hợp khi mã phải thực hiện dọn dẹp và sau đó khởi động lại:```python id="2sx6wj"
try:
risky()
except:
cleanup()
raise
```## 34,14`else`Khối
A`try`tuyên bố có thể có một`else`khối.```python id="ne9sbh"
try:
value = parse()
except ValueError:
value = default
else:
use(value)
```các`else`khối chỉ chạy nếu`try`khối hoàn thành mà không có ngoại lệ.
Nó không chạy nếu:```text id="tmvff1"
the try block raises
the try block returns
the try block breaks
the try block continues
```Ở cấp độ mã byte, đây là luồng điều khiển nhánh thông thường xung quanh khối xử lý.
## 34,15`finally`Khối
A`finally`khối chạy khi điều khiển rời khỏi`try`khối.```python id="sk93di"
try:
risky()
finally:
cleanup()
```Quá trình dọn dẹp diễn ra cho:```text id="6kjx4b"
normal fallthrough
return
exception
break
continue
```Ví dụ:```python id="pkbx1h"
def f():
try:
return 1
finally:
print("cleanup")
```Việc trả lại đang chờ xử lý trong khi`finally`cơ thể chạy.
Nếu`finally`body tăng lên, nó thay thế phần trả về đang chờ xử lý:```python id="a8odfm"
def f():
try:
return 1
finally:
raise RuntimeError("cleanup failed")
```Chức năng này tăng`RuntimeError`thay vì quay lại`1`.
## 34,16`return`Bên trong`finally`MỘT`return`bên trong`finally`ghi đè các ngoại lệ hoặc trả về trước đó.```python id="lip58q"
def f():
try:
raise ValueError("bad")
finally:
return 10
```Điều này trả về`10`. các`ValueError`bị đàn áp.
Hành vi này là hợp pháp nhưng nguy hiểm. Nó có thể che giấu lỗi.
Trong thời gian chạy,`finally`khối điều khiển đường thoát cuối cùng. Nếu nó trả về, kết quả trả về đó sẽ trở thành kết quả của hàm.
## 34.17 Trình quản lý bối cảnh và ngoại lệ
A`with`tuyên bố được xây dựng trên xử lý ngoại lệ.```python id="0710p4"
with manager as value:
body(value)
```Về mặt khái niệm:```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)
```Nếu như`__exit__`trả về giá trị thực, ngoại lệ bị loại bỏ.
Đây là cách người quản lý bối cảnh có thể triển khai khôi phục giao dịch, đóng tệp, khóa, trạng thái tạm thời và dọn dẹp tài nguyên.
## 34,18`with`Mã byte
A`with`câu lệnh biên dịch thành mã byte:```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
```Trình thông dịch phải giữ đủ trạng thái để gọi`__exit__`ngay cả khi cơ thể tăng lên.
Điều này làm cho`with`một dạng có cấu trúc của`try/finally`cộng với việc ngăn chặn ngoại lệ.
## 34.19 Ngoại lệ có chuỗi
Python ghi lại bối cảnh ngoại lệ.
Ví dụ:```python id="vpsoic"
try:
int("x")
except ValueError:
raise RuntimeError("parse failed")
```các`RuntimeError`có bối cảnh trỏ đến bản gốc`ValueError`.
Đầu ra Traceback có nội dung như sau:```text id="9g0agc"
During handling of the above exception, another exception occurred
```Sử dụng chuỗi rõ ràng`from`:
```python id="iw2vuv"
raise RuntimeError("parse failed") from exc
```Ngăn chặn việc sử dụng ngữ cảnh:```python id="gg994p"
raise RuntimeError("parse failed") from None
```Các đối tượng ngoại lệ có các trường như:```text id="pqfm3s"
__context__
__cause__
__suppress_context__
__traceback__
```## 34.20 Trạng thái ngoại lệ trong Trạng thái luồng
Ngoại lệ hoạt động được lưu trữ ở trạng thái luồng trình thông dịch.
Đây là lý do tại sao hàm C có thể báo hiệu lỗi bằng cách trả về`NULL`trong khi để lại ngoại lệ ở nơi khác.
Về mặt khái niệm:```text id="y794el"
thread state
current exception
handled exception stack
```Khi trình trợ giúp C bị lỗi:```c id="gctod1"
PyErr_SetString(PyExc_ValueError, "bad");
return NULL;
```Người gọi kiểm tra giá trị trả về và biết ngoại lệ được đặt.
Vòng đánh giá sau đó sẽ truyền bá hoặc xử lý nó.
## 34.21 Quy ước lỗi API C
Các hàm API CPython C thường tuân theo một trong một số quy ước về lỗi.
Các hàm trả về con trỏ:```c id="f6xl1n"
PyObject *obj = PyLong_FromString(text, NULL, 10);
if (obj == NULL) {
return NULL;
}
```Hàm trả về số nguyên:```c id="tj37v0"
int rc = PyObject_SetAttrString(obj, "x", value);
if (rc < 0) {
return NULL;
}
```Kiểm tra giống Boolean có thể trả lại`1`, `0`, hoặc`-1`:
```c id="71hl88"
int ok = PyObject_IsTrue(obj);
if (ok < 0) {
return NULL;
}
```Kết quả lỗi và trạng thái ngoại lệ phải giống nhau. Trả lại mã lỗi mà không đặt ngoại lệ thường là lỗi.
## 34,22 Tăng từ C
Mã C tăng ngoại lệ Python bằng cách đặt trạng thái lỗi.
Ví dụ:```c id="c8dwxx"
PyErr_SetString(PyExc_TypeError, "expected integer");
return NULL;
```Đối với tin nhắn được định dạng:```c id="npt9uu"
PyErr_Format(PyExc_ValueError, "bad value: %d", value);
return NULL;
```Để truyền bá một ngoại lệ hiện có, mã C trả về trọng điểm lỗi mà không ghi đè ngoại lệ đó.
Mẫu này cho phép lỗi di chuyển qua nhiều hàm C cho đến khi vòng đánh giá tìm thấy trình xử lý Python hoặc thoát lên cấp cao nhất.
## 34.23 Xóa ngoại lệ
Đôi khi mã C cố ý xử lý một ngoại lệ và xóa nó.
Về mặt khái niệm:```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;
}
}
```Xóa ngoại lệ sai có thể che giấu các lỗi thực sự.
Mã Python có mẫu tương tự:```python id="rcjz53"
try:
value = obj.optional
except AttributeError:
value = default
```Nguyên tắc chính là chỉ bắt ngoại lệ mà bạn định xử lý.
## 34.24 Ngoại lệ trong quá trình xử lý ngoại lệ
Trình xử lý có thể đưa ra một ngoại lệ khác.```python id="gc39fq"
try:
risky()
except ValueError:
recover_badly()
```Nếu như`recover_badly()`tăng lên, ngoại lệ mới sẽ thay thế kết quả bình thường của trình xử lý trong khi vẫn giữ nguyên bối cảnh về ngoại lệ ban đầu.
MỘT`finally`khối cũng có thể tăng lên trong quá trình dọn dẹp.
CPython phải bảo toàn đủ trạng thái để xây dựng chuỗi truy nguyên hữu ích.
## 34.25 Ngoại lệ và Trình tạo
Máy phát điện sử dụng ngoại lệ để kiểm soát.
Một máy phát điện kết thúc bằng cách nâng cao`StopIteration`nội bộ cho người gọi.```python id="dz6ljh"
def gen():
yield 1
g = gen()
next(g)
next(g)
```thứ hai`next(g)`tăng lên`StopIteration`.
MỘT`return value`bên trong một máy phát điện trở thành giá trị gắn liền với`StopIteration`.
```python id="r2gskh"
def gen():
return 42
yield
g = gen()
try:
next(g)
except StopIteration as exc:
print(exc.value)
```Đầu ra là:```text id="4y674v"
42
```Sử dụng hoàn thiện máy phát điện`GeneratorExit`.
## 34.26 PEP 479 và Máy phát điện
A`StopIteration`vô tình được nâng lên bên trong thân máy phát điện được chuyển thành`RuntimeError`.
Ví dụ:```python id="muj1jf"
def gen():
raise StopIteration
yield
```Điều này tránh được những lỗi nhỏ khi vô tình`StopIteration`âm thầm chấm dứt máy phát điện.
Giao thức máy phát điện vẫn sử dụng`StopIteration`ở ranh giới. Việc chuyển đổi áp dụng bên trong việc thực thi trình tạo.
## 34.27 Ngoại lệ và Coroutine
Coroutine cũng sử dụng các đường dẫn ngoại lệ để hủy và xử lý lỗi.
Một coroutine được chờ đợi có thể hoàn thành bình thường:```text id="tp92nc"
return value
```hoặc thất bại:```text id="cvojm0"
raise exception
```Việc hủy bỏ thường được thể hiện bằng một ngoại lệ được đưa vào quá trình thực thi coroutine.
Khung coroutine tiếp tục với một ngoại lệ thay vì giá trị bình thường.
Điều này có nghĩa là các khung không đồng bộ phụ thuộc sâu sắc vào mô hình tạm dừng, nối lại và truyền ngoại lệ khung của CPython.
## 34.28 Nhóm ngoại lệ
Python hiện đại bao gồm các nhóm ngoại lệ để biểu diễn nhiều ngoại lệ cùng nhau.```python id="0cenmj"
raise ExceptionGroup("many", [ValueError("a"), TypeError("b")])
```Chúng được xử lý bằng`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)
```Điều này quan trọng đối với các chương trình đồng thời trong đó nhiều tác vụ có thể bị lỗi cùng một lúc.
Trình thông dịch phải phân chia và khớp các nhóm ngoại lệ trên`except*`người xử lý.
## 34,29`except*`
`except*`khác với bình thường`except`.
Bình thường`except`chọn một trình xử lý cho một ngoại lệ đang hoạt động.`except*`có thể chia một nhóm ngoại lệ và định tuyến các ngoại lệ phụ khác nhau cho các trình xử lý khác nhau.
Về mặt khái niệm:```text id="7m7ae6"
ExceptionGroup(ValueError, TypeError)
except* ValueError receives ValueError subgroup
except* TypeError receives TypeError subgroup
```Bất kỳ nhóm con nào chưa từng có sẽ tiếp tục lan truyền.
Điều này bổ sung khả năng xử lý nhiều lỗi có cấu trúc mà không loại bỏ các danh tính ngoại lệ riêng lẻ.
## 34.30 Lưu giữ dấu vết
Dấu vết giúp giữ cho khung hình luôn sống động.
Ví dụ:```python id="7vw5yb"
saved = None
def f():
big = bytearray(100_000_000)
raise RuntimeError
try:
f()
except RuntimeError as exc:
saved = exc
```Ngoại lệ đã lưu có thể giữ lại dấu vết của nó. Traceback giữ lại khung. Khung giữ lại biến cục bộ`big`.
Chuỗi lưu giữ:```text id="9fqt5r"
saved exception
traceback
frame
locals
big bytearray
```Đây là lý do tại sao các ngoại lệ tồn tại lâu dài có thể giữ được bộ nhớ đáng kể.
## 34.31 Làm sạch dấu vết
Bạn có thể tránh việc lưu giữ bằng cách không lưu trữ các ngoại lệ lâu hơn mức cần thiết hoặc bằng cách xóa các tham chiếu truy nguyên.```python id="tb2iqr"
try:
f()
except RuntimeError as exc:
handle(exc)
exc = None
```Để dọn dẹp rõ ràng:```python id="smrtfq"
exc.__traceback__ = None
```Hãy sử dụng tính năng này một cách cẩn thận vì việc truy nguyên rất hữu ích cho việc gỡ lỗi.
Nguyên tắc chung là: lưu trữ ngoại lệ lưu trữ bối cảnh.
## 34,32`finally`và dọn dẹp tham chiếu`finally`thường được sử dụng để giải phóng tài nguyên.```python id="iibrsm"
resource = acquire()
try:
use(resource)
finally:
resource.close()
```Hình thức ưa thích để quản lý tài nguyên thường là trình quản lý bối cảnh:```python id="tfg64f"
with acquire() as resource:
use(resource)
```Cả hai hình thức đều phụ thuộc vào máy móc xử lý ngoại lệ để đảm bảo quá trình dọn dẹp diễn ra khi điều khiển rời khỏi khối được bảo vệ.
## 34.33 Ngoại lệ và`__del__`Các ngoại lệ được nêu ra trong bộ hoàn thiện đối tượng được xử lý đặc biệt.```python id="6vbsh5"
class C:
def __del__(self):
raise RuntimeError("bad finalizer")
```Một ngoại lệ từ`__del__`không thể truyền bình thường tới mã người dùng tại thời điểm xảy ra việc thu gom rác. CPython báo cáo nó thông qua máy móc ngoại lệ không thể xử lý được.
Đây là lý do tại sao những người quyết toán nên tránh raise.
## 34.34 Ngoại lệ không thể xử lý được
Một số trường hợp ngoại lệ xảy ra khi việc truyền bá bình thường là không thể.
Ví dụ:```text id="ydy6r1"
__del__ finalizers
weakref callbacks
some cleanup hooks
background finalization contexts
```CPython báo cáo những điều này thông qua việc xử lý ngoại lệ không thể xử lý được, có sẵn thông qua`sys.unraisablehook`.
Điều này bảo tồn thông tin chẩn đoán mà không phá vỡ các bối cảnh luồng điều khiển không thể thực hiện được.
## 34,35 Ngoại lệ và nhập khẩu
Lỗi nhập khẩu sử dụng ngoại lệ.```python id="54d93q"
import missing_module
```tăng lên`ModuleNotFoundError`.
Quá trình nhập có thể không thành công ở một số giai đoạn:```text id="0yjm9i"
module search
loader creation
source reading
bytecode loading
module execution
submodule import
package initialization
```Nếu quá trình thực thi mô-đun tăng lên thì quá trình nhập không thành công với ngoại lệ đó.
Vì việc nhập mô-đun chạy mã cấp cao nhất của nó nên các ngoại lệ tùy ý có thể phát sinh trong quá trình nhập.
## 34.36 Ngoại lệ và tra cứu thuộc tính
Thiếu tăng tra cứu thuộc tính`AttributeError`.
```python id="qcd62q"
obj.missing
```Nhưng tra cứu tùy chỉnh có thể nâng cao bất cứ điều gì:```python id="otwoa7"
class C:
@property
def x(self):
raise RuntimeError("failed")
```Điều này quan trọng đối với`getattr`, `hasattr`và các khung động.`hasattr`đánh bắt`AttributeError`, không phải là ngoại lệ tùy ý.
## 34.37 Ngoại lệ và lặp lại
Sử dụng lặp lại`StopIteration`.
Tương đương thủ công:```python id="0pw412"
it = iter(xs)
while True:
try:
x = next(it)
except StopIteration:
break
body(x)
```Ở cấp độ bytecode, các lệnh vòng lặp nhận biết sự cạn kiệt của vòng lặp và phân nhánh ra khỏi vòng lặp.
Đây là một đường dẫn ngoại lệ bình thường, được mong đợi.
## 34.38 Ngoại lệ và So khớp mẫu
Việc so khớp mẫu có thể sử dụng lỗi trong nội bộ mà không để lộ ra các ngoại lệ đối với các kết quả không khớp thông thường.```python id="vlqdr7"
match value:
case {"x": x}:
...
case _:
...
```Công cụ phù hợp phải phân biệt:```text id="b58239"
pattern does not match
operation raises a real exception
```Một mẫu không thành công sẽ chuyển sang trường hợp tiếp theo. Một ngoại lệ thực sự nên được tuyên truyền.
## 34.39 Ngoại lệ và hướng dẫn mã byte
Nhiều hướng dẫn mã byte có thể tăng lên.
Ví dụ:
| Loại hướng dẫn | Có thể thất bại |
|---|---|
|`LOAD_GLOBAL` | `NameError` |
| `LOAD_ATTR` | `AttributeError`hoặc lỗi mô tả tùy ý |
|`BINARY_OP` | `TypeError`, `ZeroDivisionError`, ngoại lệ của người dùng |
|`CALL`| Lỗi đối số hoặc ngoại lệ callee |
|`IMPORT_NAME` | `ImportError`, lỗi thực thi mô-đun tùy ý |
|`FOR_ITER`| Ngoại lệ của trình vòng lặp khác với sự cạn kiệt thông thường |
|`STORE_ATTR` | `AttributeError`, lỗi mô tả |
|`BUILD_LIST`| Lỗi phân bổ bộ nhớ |
Vòng đánh giá phải cho rằng hầu hết các hướng dẫn đều có thể thất bại.
## 34.40 An toàn ngoại lệ trong vòng đánh giá
Mọi việc thực hiện lệnh đều cần một đường dẫn lỗi.
Đơn giản hóa:```c id="b2kgao"
PyObject *result = operation();
if (result == NULL) {
goto error;
}
push(result);
```Đường dẫn lỗi phải:```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
```Đây là một trong những phần khó nhất khi triển khai trình thông dịch.
## 34.41 Ngoại lệ và tính tham chiếu
Các ngoại lệ là các đối tượng Python nên chúng được tính theo tham chiếu.
Dấu vết, khung và các đối tượng ngoại lệ có thể hình thành các chu trình:```text id="olvwqs"
exception
traceback
frame
locals
exception
```CPython có hành vi dọn dẹp đặc biệt xung quanh các biến ngoại lệ để giảm các chu kỳ này. Trình thu gom rác tuần hoàn cũng có thể thu thập các chu kỳ không thể truy cập được.
Tuy nhiên, việc lưu trữ ngoại lệ có thể kéo dài tuổi thọ của đối tượng.
## 34.42 Chuẩn hóa ngoại lệ
Trong nội bộ, CPython thường cần bình thường hóa một ngoại lệ để có một trường hợp ngoại lệ cụ thể.
Các hình thức đầu vào:```python id="dzbie3"
raise ValueError
raise ValueError("bad")
```Cả hai đều trở thành một loại ngoại lệ và một thể hiện.
Chuẩn hóa đảm bảo mã sau này có thể kiểm tra:```text id="zqj7jd"
exception class
exception instance
traceback
cause
context
notes
```Việc chuẩn hóa ngoại lệ có thể tự nó thất bại nếu việc xây dựng phiên bản ngoại lệ không thành công.
## 34.43 Ghi chú ngoại lệ
Các ngoại lệ của Python có thể mang theo ghi chú.```python id="j1c2ow"
try:
raise ValueError("bad")
except ValueError as exc:
exc.add_note("while parsing config")
raise
```Ghi chú cung cấp văn bản chẩn đoán bổ sung trong đầu ra truy nguyên.
Chúng được lưu trữ trên đối tượng ngoại lệ và không thay đổi hành vi khớp.
## 34.44 Lỗi cú pháp
Lỗi cú pháp cũng là ngoại lệ, nhưng chúng phát sinh trước khi thực thi mã byte thông thường.```python id="7eglix"
eval("if")
```tăng lên`SyntaxError`.
Các ngoại lệ trong giai đoạn biên dịch bao gồm:```text id="11480v"
SyntaxError
IndentationError
TabError
```Những điều này xảy ra trong quá trình phân tích cú pháp hoặc biên dịch, trước khi vòng đánh giá thực thi mã kết quả.
## 34,45 Lỗi bộ nhớ
Lỗi phân bổ tăng lên`MemoryError`khi CPython có thể báo cáo nó.
Các hoạt động ví dụ có thể phân bổ:```text id="u0gk53"
creating objects
building lists
creating strings
expanding dictionaries
constructing tracebacks
formatting exception messages
```Bản thân việc xử lý ngoại lệ có thể phân bổ, do đó, thời gian chạy phải cẩn thận khi báo cáo tình trạng bộ nhớ thấp.
## 34.46 Tín hiệu và ngắt bàn phím`KeyboardInterrupt`thường được nâng lên khi trình thông dịch xử lý tín hiệu ngắt như Ctrl-C.
Tín hiệu không được xử lý theo hướng dẫn máy tùy ý. CPython ghi lại trạng thái tín hiệu đang chờ xử lý và kiểm tra tại các điểm an toàn trong vòng đánh giá.
Khi được xử lý, trình thông dịch sẽ đưa ra`KeyboardInterrupt`trong luồng thực thi.
Đây là một ví dụ khác về máy móc ngoại lệ phục vụ luồng điều khiển bên ngoài.
## 34.47 Thoát hệ thống`sys.exit()`tăng lên`SystemExit`.
```python id="qyxowo"
import sys
sys.exit(1)
```Nếu không bị bắt, trình thông dịch sẽ thoát với trạng thái đã cho.
Bởi vì nó là một ngoại lệ nên nó có thể bị bắt:```python id="yl2p70"
try:
sys.exit(1)
except SystemExit:
print("caught")
```Đây là lý do tại sao`SystemExit`bắt nguồn trực tiếp từ`BaseException`, rất rộng`except Exception`trình xử lý thường không ngăn chặn quá trình thoát.
## 34.48 Những hiểu lầm phổ biến
| Hiểu lầm | Đúng mẫu |
|---|---|
| Ngoại lệ chỉ dành cho lỗi | Họ cũng triển khai các giao thức lặp, thoát, hủy và điều khiển |
| Truy nguyên chỉ là văn bản | Đó là một chuỗi các bản ghi khung |
|`except Exception`nắm bắt mọi thứ | Nó không bắt được tất cả`BaseException`lớp con |
|`finally`luôn giữ nguyên lỗi ban đầu | Lợi nhuận hoặc tăng trong`finally`có thể thay thế nó |
|`with`chỉ các cuộc gọi`close`| Nó gọi`__enter__`Và`__exit__`, với các chi tiết ngoại lệ |
|`hasattr`không có tác dụng phụ | Nó thực hiện tra cứu thuộc tính và có thể chạy mã người dùng |
| Mã C trả về trực tiếp các đối tượng ngoại lệ | Thông thường nó đặt trạng thái ngoại lệ và trả về lỗi canh gác |
| Lưu trữ ngoại lệ là vô hại | Nó có thể giữ lại dấu vết, khung và cục bộ lớn |
## 34,49 Chiến lược đọc
Để nghiên cứu cách xử lý ngoại lệ, hãy phân tích các ví dụ nhỏ.
Bắt đầu với:```python id="d0qfxt"
def f(x):
try:
return 10 / x
except ZeroDivisionError:
return 0
```Sau đó kiểm tra:```python id="lefgod"
import dis
dis.dis(f)
```Đồng thời kiểm tra đầu ra của bảng ngoại lệ khi có sẵn:```python id="mj1mrk"
dis.show_code(f)
```Sau đó kiểm tra:```python id="fo0fk2"
try/finally
try/except/else
with statements
raise from
bare raise
generators with return
ExceptionGroup and except*
```Đối với mỗi trường hợp, theo dõi:```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 Tóm tắt chương
Xử lý ngoại lệ trong CPython là một hệ thống luồng điều khiển có cấu trúc. Nó sử dụng các đối tượng ngoại lệ, lưu trữ ngoại lệ trạng thái luồng, giải phóng khung, xây dựng truy nguyên, bảng ngoại lệ, khôi phục ngăn xếp, khớp trình xử lý và các khối dọn dẹp.
Mô hình cốt lõi là:```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ác ngoại lệ kết nối nhiều phần của thời gian chạy: thực thi mã byte, lệnh gọi hàm, trình quản lý ngữ cảnh, trình tạo, coroutine, nhập, tra cứu thuộc tính, quy ước lỗi API C, xử lý tín hiệu và quản lý bộ nhớ.
Hiểu các ngoại lệ có nghĩa là hiểu cả cách xử lý lỗi và phần chính của cơ chế điều khiển luồng của Python.