8. Lỗi và ngoại lệ

Cho đến nay, các thông báo lỗi chưa được đề cập nhiều nhưng nếu bạn đã thử các ví dụ thì có thể bạn đã thấy một số ví dụ. Có (ít nhất) hai loại lỗi có thể phân biệt được: syntax errorsexceptions.

8.1. Lỗi cú pháp

Lỗi cú pháp, còn được gọi là lỗi phân tích cú pháp, có lẽ là loại khiếu nại phổ biến nhất mà bạn gặp phải khi vẫn đang học Python:

>>> while True print('Xin chào thế giới')
  Tệp "<stdin>", dòng 1
    while True print('Xin chào thế giới')
               ^^ ^^^
Lỗi cú pháp: cú pháp không hợp lệ

Trình phân tích cú pháp lặp lại dòng vi phạm và hiển thị các mũi tên nhỏ chỉ vào nơi phát hiện lỗi. Lưu ý rằng đây không phải lúc nào cũng là nơi cần được sửa chữa. Trong ví dụ này, lỗi được phát hiện ở hàm print(), do thiếu dấu hai chấm (':') ngay trước nó.

Tên tệp (<stdin> trong ví dụ của chúng tôi) và số dòng được in để bạn biết nơi cần tìm trong trường hợp dữ liệu đầu vào đến từ một tệp.

8.2. Ngoại lệ

Ngay cả khi một câu lệnh hoặc biểu thức đúng về mặt cú pháp, nó vẫn có thể gây ra lỗi khi cố gắng thực thi nó. Các lỗi được phát hiện trong quá trình thực thi được gọi là exceptions và không nghiêm trọng vô điều kiện: bạn sẽ sớm học cách xử lý chúng trong các chương trình Python. Tuy nhiên, hầu hết các trường hợp ngoại lệ không được chương trình xử lý và dẫn đến thông báo lỗi như được hiển thị ở đây:

>>> 10 * (1/0)
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
    10 * (1/0)
          ~^~
ZeroDivisionError: chia cho 0
>>> 4 + thư rác*3
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
    4 + thư rác*3
        ^^ ^^
NameError: tên 'spam' không được xác định
>>> '2' + 2
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
    '2' + 2
    ~~~~^~~
TypeError: chỉ có thể nối str (không phải "int") với str

Dòng cuối cùng của thông báo lỗi cho biết điều gì đã xảy ra. Các ngoại lệ có nhiều loại khác nhau và loại được in như một phần của thông báo: các loại trong ví dụ là ZeroDivisionError, NameErrorTypeError. Chuỗi được in dưới dạng loại ngoại lệ là tên của ngoại lệ tích hợp đã xảy ra. Điều này đúng với tất cả các ngoại lệ có sẵn, nhưng không nhất thiết phải đúng với các ngoại lệ do người dùng xác định (mặc dù đây là một quy ước hữu ích). Tên ngoại lệ tiêu chuẩn là số nhận dạng tích hợp (không phải từ khóa dành riêng).

Phần còn lại của dòng cung cấp thông tin chi tiết dựa trên loại ngoại lệ và nguyên nhân gây ra ngoại lệ đó.

Phần trước của thông báo lỗi hiển thị bối cảnh xảy ra ngoại lệ, dưới dạng truy nguyên ngăn xếp. Nói chung, nó chứa một dòng nguồn liệt kê truy nguyên ngăn xếp; tuy nhiên, nó sẽ không hiển thị các dòng được đọc từ đầu vào tiêu chuẩn.

Ngoại lệ tích hợp liệt kê các ngoại lệ tích hợp và ý nghĩa của chúng.

8.3. Xử lý ngoại lệ

Có thể viết chương trình xử lý các ngoại lệ được chọn. Hãy xem ví dụ sau, ví dụ này yêu cầu người dùng nhập dữ liệu cho đến khi nhập một số nguyên hợp lệ, nhưng cho phép người dùng làm gián đoạn chương trình (sử dụng Control-C hoặc bất kỳ thứ gì mà hệ điều hành hỗ trợ); lưu ý rằng sự gián đoạn do người dùng tạo được báo hiệu bằng cách đưa ra ngoại lệ KeyboardInterrupt.

>>> trong khi Đúng:
... thử:
... x = int(input("Xin vui lòng nhập một số:"))
... nghỉ
... ngoại trừ ValueError:
... print("Rất tiếc! Đó không phải là số hợp lệ. Hãy thử lại...")
...

Câu lệnh try hoạt động như sau.

  • Đầu tiên, try clause ((các) câu lệnh giữa từ khóa tryexcept) được thực thi.

  • Nếu không có ngoại lệ nào xảy ra, except clause sẽ bị bỏ qua và việc thực thi câu lệnh try kết thúc.

  • Nếu một ngoại lệ xảy ra trong quá trình thực thi mệnh đề try thì phần còn lại của mệnh đề sẽ bị bỏ qua. Sau đó, nếu loại của nó khớp với ngoại lệ được đặt tên theo từ khóa except, thì except clause sẽ được thực thi và sau đó việc thực thi tiếp tục sau khối thử/ngoại trừ.

  • Nếu một ngoại lệ xảy ra không khớp với ngoại lệ có tên trong except clause, thì nó sẽ được chuyển sang các câu lệnh try bên ngoài; nếu không tìm thấy trình xử lý nào thì đó là unhandled exception và quá trình thực thi dừng lại kèm theo thông báo lỗi.

Một câu lệnh try có thể có nhiều hơn một except clause, để chỉ định các trình xử lý cho các ngoại lệ khác nhau. Nhiều nhất một trình xử lý sẽ được thực thi. Trình xử lý chỉ xử lý các ngoại lệ xảy ra trong try clause tương ứng, không xử lý trong các trình xử lý khác của cùng một câu lệnh try. Một except clause có thể đặt tên cho nhiều trường hợp ngoại lệ, ví dụ:

... ngoại trừ RuntimeError, TypeError, NameError:
... vượt qua

Một lớp trong mệnh đề except khớp với các ngoại lệ là các thể hiện của chính lớp đó hoặc một trong các lớp dẫn xuất của nó (nhưng không phải ngược lại --- một except clause liệt kê một lớp dẫn xuất không khớp với các thể hiện của các lớp cơ sở của nó). Ví dụ: đoạn mã sau sẽ in B, C, D theo thứ tự đó:

lớp B (Ngoại lệ):
    vượt qua

lớp C(B):
    vượt qua

lớp D (C):
    vượt qua

cho cls trong [B, C, D]:
    thử:
        tăng cls()
    ngoại trừ D:
        in ("D")
    ngoại trừ C:
        in ("C")
    ngoại trừ B:
        in ("B")

Lưu ý rằng nếu except clauses bị đảo ngược (với except B đầu tiên), nó sẽ in B, B, B --- except clause phù hợp đầu tiên được kích hoạt.

Khi một ngoại lệ xảy ra, nó có thể có các giá trị liên quan, còn được gọi là arguments của ngoại lệ. Sự hiện diện và loại đối số phụ thuộc vào loại ngoại lệ.

Zz003zz có thể chỉ định một biến sau tên ngoại lệ. Biến được liên kết với phiên bản ngoại lệ thường có thuộc tính args lưu trữ các đối số. Để thuận tiện, các loại ngoại lệ dựng sẵn xác định __str__() để in tất cả các đối số mà không cần truy cập rõ ràng vào .args.

>>> thử:
... tăng Ngoại lệ('thư rác', 'trứng')
... ngoại trừ Ngoại lệ như inst:
... print(type(inst)) loại ngoại lệ # the
... print(inst.args) # arguments được lưu trữ trong .args
... print(inst) # __str__ cho phép in trực tiếp các đối số,
... # but có thể bị ghi đè trong các lớp con ngoại lệ
... x, y = inst.args # unpack args
... in('x =', x)
... in('y =', y)
...
<lớp 'Ngoại lệ'>
('thư rác', 'trứng')
('thư rác', 'trứng')
x = thư rác
y = trứng

Đầu ra __str__() của ngoại lệ được in dưới dạng phần cuối cùng ('chi tiết') của thông báo về các ngoại lệ chưa được xử lý.

BaseException là lớp cơ sở chung của tất cả các ngoại lệ. Một trong các lớp con của nó, Exception, là lớp cơ sở của tất cả các trường hợp ngoại lệ không gây tử vong. Các ngoại lệ không phải là lớp con của Exception thường không được xử lý vì chúng được sử dụng để chỉ ra rằng chương trình sẽ kết thúc. Chúng bao gồm SystemExit được kích hoạt bởi sys.exit()KeyboardInterrupt được kích hoạt khi người dùng muốn làm gián đoạn chương trình.

Exception có thể được sử dụng làm ký tự đại diện để bắt (gần như) mọi thứ. Tuy nhiên, cách thực hành tốt là càng cụ thể càng tốt với các loại ngoại lệ mà chúng tôi dự định xử lý và cho phép mọi ngoại lệ không mong muốn được phổ biến.

Mẫu phổ biến nhất để xử lý Exception là in hoặc ghi nhật ký ngoại lệ rồi khởi động lại nó (cho phép người gọi cũng xử lý ngoại lệ):

hệ thống nhập khẩu

thử:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
ngoại trừ OSError  lỗi:
    print("Lỗi hệ điều hành:", err)
ngoại trừ ValueError:
    print("Không thể chuyển đổi dữ liệu thành số nguyên.")
ngoại trừ Ngoại lệ  lỗi:
    print(f"Không mong đợi {err=}, {type(err)=}")
    nâng cao

Câu lệnh try ... except có một else clause tùy chọn, khi có mặt, nó phải tuân theo tất cả except clauses. Nó rất hữu ích cho mã phải được thực thi nếu try clause không đưa ra ngoại lệ. Ví dụ:

cho arg trong sys.argv[1:]:
    thử:
        f = open(arg, 'r')
    ngoại trừ OSError:
        print('không thể mở', arg)
    khác:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

Việc sử dụng mệnh đề else sẽ tốt hơn việc thêm mã bổ sung vào mệnh đề try vì nó tránh được việc vô tình bắt gặp một ngoại lệ không được tạo ra bởi mã được bảo vệ bởi câu lệnh try ... except.

Trình xử lý ngoại lệ không chỉ xử lý các ngoại lệ xảy ra ngay lập tức trong try clause mà còn xử lý các ngoại lệ xảy ra bên trong các hàm được gọi (thậm chí gián tiếp) trong try clause. Ví dụ:

>>> giải quyết this_fails():
... x = 1/0
...
>>> thử:
... this_fails()
... ngoại trừ ZeroDivisionError  lỗi:
... print('Xử lý lỗi thời gian chạy:', err)
...
Xử lý lỗi thời gian chạy: chia cho 0

8.4. Đưa ra ngoại lệ

Câu lệnh raise cho phép lập trình viên buộc một ngoại lệ được chỉ định xảy ra. Ví dụ:

>>> nâng cao NameError('HiThere')
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
    nâng cao NameError('HiThere')
TênLỗi: Xin chào

Đối số duy nhất cho raise cho biết ngoại lệ sẽ được đưa ra. Đây phải là một phiên bản ngoại lệ hoặc một lớp ngoại lệ (một lớp bắt nguồn từ BaseException, chẳng hạn như Exception hoặc một trong các lớp con của nó). Nếu một lớp ngoại lệ được thông qua, nó sẽ được khởi tạo ngầm bằng cách gọi hàm tạo của nó mà không có đối số:

tăng ValueError # shorthand cho 'tăng ValueError()'

Nếu bạn cần xác định xem một ngoại lệ có được nêu ra hay không nhưng không có ý định xử lý nó, một dạng câu lệnh raise đơn giản hơn sẽ cho phép bạn đưa ra lại ngoại lệ đó:

>>> thử:
... raise NameError('HiThere')
... ngoại trừ NameError:
... print('Một ngoại lệ đã bay qua!')
... nâng cao
...
Một ngoại lệ đã bay qua!
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 2, trong <module>
    nâng cao NameError('HiThere')
TênLỗi: Xin chào

8.5. Chuỗi ngoại lệ

Nếu một ngoại lệ chưa được xử lý xảy ra bên trong phần except, thì ngoại lệ đó sẽ được xử lý kèm theo và được bao gồm trong thông báo lỗi:

>>> try:
...     open("database.sqlite")
... except OSError:
...     raise RuntimeError("unable to handle error")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    open("database.sqlite")
    ~~~~^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError("unable to handle error")
RuntimeError: unable to handle error

Để chỉ ra rằng một ngoại lệ là hệ quả trực tiếp của một ngoại lệ khác, câu lệnh raise cho phép một mệnh đề from tùy chọn:

# exc phải là trường hợp ngoại lệ hoặc Không có.
tăng RuntimeError từ exec

Điều này có thể hữu ích khi bạn đang chuyển đổi các ngoại lệ. Ví dụ:

>>> def func():
...     raise ConnectionError
...
>>> try:
...     func()
... except ConnectionError as exc:
...     raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    func()
    ~~~~^^
  File "<stdin>", line 2, in func
ConnectionError

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

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError('Failed to open database') from exc
RuntimeError: Failed to open database

Nó cũng cho phép vô hiệu hóa chuỗi ngoại lệ tự động bằng cách sử dụng thành ngữ from None

>>> thử:
... open('database.sqlite')
... ngoại trừ OSError:
... tăng RuntimeError từ Không 
...
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 4, trong <module>
    tăng RuntimeError từ Không có
Lỗi thời gian chạy

Để biết thêm thông tin về cơ chế xích, hãy xem Ngoại lệ tích hợp.

8.6. Ngoại lệ do người dùng xác định

Các chương trình có thể đặt tên cho các ngoại lệ của riêng mình bằng cách tạo một lớp ngoại lệ mới (xem Lớp học để biết thêm về các lớp Python). Các ngoại lệ thường được bắt nguồn từ lớp Exception, trực tiếp hoặc gián tiếp.

Các lớp ngoại lệ có thể được định nghĩa để thực hiện bất kỳ điều gì mà bất kỳ lớp nào khác có thể làm, nhưng thường được giữ đơn giản, thường chỉ cung cấp một số thuộc tính cho phép các trình xử lý ngoại lệ trích xuất thông tin về lỗi.

Hầu hết các ngoại lệ đều được xác định bằng tên kết thúc bằng "Lỗi", tương tự như cách đặt tên cho các ngoại lệ tiêu chuẩn.

Nhiều mô-đun tiêu chuẩn xác định các ngoại lệ của riêng chúng để báo cáo các lỗi có thể xảy ra trong các hàm mà chúng xác định.

8.7. Xác định hành động dọn dẹp

Câu lệnh try có một mệnh đề tùy chọn khác nhằm xác định các hành động dọn dẹp phải được thực thi trong mọi trường hợp. Ví dụ:

>>> thử:
... nâng Bàn phímInterrupt
... cuối cùng:
... print('Tạm biệt thế giới!')
...
Tạm biệt thế giới!
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 2, trong <module>
    nâng Bàn phímNgắt
Bàn PhímNgắt

Nếu có mệnh đề finally, mệnh đề finally sẽ thực thi như tác vụ cuối cùng trước khi câu lệnh try hoàn thành. Mệnh đề finally vẫn chạy dù câu lệnh try có tạo ra ngoại lệ hay không. Các điểm sau đây thảo luận về các trường hợp phức tạp hơn khi xảy ra ngoại lệ:

  • Nếu một ngoại lệ xảy ra trong quá trình thực thi mệnh đề try, ngoại lệ đó có thể được xử lý bằng mệnh đề except. Nếu ngoại lệ không được xử lý bởi mệnh đề except, ngoại lệ đó sẽ được đưa ra lại sau khi mệnh đề finally được thực thi.

  • Một ngoại lệ có thể xảy ra trong quá trình thực thi mệnh đề except hoặc else. Một lần nữa, ngoại lệ lại xuất hiện sau khi mệnh đề finally được thực thi.

  • Nếu mệnh đề finally thực thi câu lệnh break, continue hoặc return, các ngoại lệ sẽ không được đưa ra lại. Điều này có thể gây nhầm lẫn và do đó không được khuyến khích. Từ phiên bản 3.14, trình biên dịch sẽ phát ra SyntaxWarning cho nó (xem PEP 765).

  • Nếu câu lệnh try đạt đến câu lệnh break, continue hoặc return, mệnh đề finally sẽ thực thi ngay trước khi thực thi câu lệnh break, continue hoặc return.

  • Nếu mệnh đề finally bao gồm câu lệnh return thì giá trị được trả về sẽ là giá trị từ câu lệnh return của mệnh đề finally, chứ không phải giá trị từ câu lệnh return của mệnh đề try. Điều này có thể gây nhầm lẫn và do đó không được khuyến khích. Từ phiên bản 3.14, trình biên dịch sẽ phát ra SyntaxWarning cho nó (xem PEP 765).

Ví dụ:

>>> def bool_return():
... thử:
... trả về Đúng
... cuối cùng:
... trả về Sai
...
>>> bool_return()
sai

Một ví dụ phức tạp hơn:

>>> def chia(x, y):
... thử:
... kết quả = x/y
... ngoại trừ ZeroDivisionError:
... print("chia cho 0!")
... khác:
... print("kết quả là", kết quả)
... cuối cùng:
... print("thực thi mệnh đề cuối cùng")
...
>>> chia(2, 1)
kết quả là 2.0
thực thi mệnh đề cuối cùng
>>> chia(2, 0)
chia cho số không!
thực thi mệnh đề cuối cùng
>>> chia("2", "1")
thực thi mệnh đề cuối cùng
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
    chia ("2", "1")
    ~~~~~~ ^^ ^^ ^^ ^^ ^^
  Tệp "<stdin>", dòng 3, được chia
    kết quả = x/y
             ~~^~~
TypeError: (các) loại toán hạng không được hỗ trợ cho /: 'str' và 'str'

Như bạn có thể thấy, mệnh đề finally được thực thi trong mọi trường hợp. TypeError được nâng lên bằng cách chia hai chuỗi không được mệnh đề except xử lý và do đó được nâng lên lại sau khi mệnh đề finally được thực thi.

Trong các ứng dụng trong thế giới thực, mệnh đề finally rất hữu ích để giải phóng các tài nguyên bên ngoài (chẳng hạn như tệp hoặc kết nối mạng), bất kể việc sử dụng tài nguyên đó có thành công hay không.

8.8. Hành động dọn dẹp được xác định trước

Một số đối tượng xác định các hành động dọn dẹp tiêu chuẩn sẽ được thực hiện khi đối tượng đó không còn cần thiết nữa, bất kể thao tác sử dụng đối tượng đó thành công hay thất bại. Hãy xem ví dụ sau đây, cố gắng mở một tập tin và in nội dung của nó ra màn hình.

cho dòng trong open("myfile.txt"):
    in(dòng, kết thúc="")

Vấn đề với mã này là nó để tệp mở trong một khoảng thời gian không xác định sau khi phần mã này thực thi xong. Đây không phải là vấn đề trong các tập lệnh đơn giản nhưng có thể là vấn đề đối với các ứng dụng lớn hơn. Câu lệnh with cho phép sử dụng các đối tượng như tệp theo cách đảm bảo chúng luôn được dọn dẹp kịp thời và chính xác.

với open("myfile.txt")  f:
    cho dòng trong f:
        in(dòng, kết thúc="")

Sau khi câu lệnh được thực thi, tệp f luôn bị đóng, ngay cả khi gặp sự cố khi xử lý các dòng. Các đối tượng, như tệp, cung cấp các hành động dọn dẹp được xác định trước sẽ chỉ ra điều này trong tài liệu của chúng.

8.9. Đưa ra và xử lý nhiều ngoại lệ không liên quan

Có những tình huống cần phải báo cáo một số trường hợp ngoại lệ đã xảy ra. Điều này thường xảy ra trong các khung công tác đồng thời, khi một số tác vụ có thể bị lỗi song song, nhưng cũng có những trường hợp sử dụng khác mong muốn tiếp tục thực thi và thu thập nhiều lỗi thay vì đưa ra ngoại lệ đầu tiên.

Zz000zz tích hợp bao gồm một danh sách các trường hợp ngoại lệ để chúng có thể được nâng lên cùng nhau. Bản thân nó là một ngoại lệ nên nó có thể bị bắt như bất kỳ ngoại lệ nào khác.

>>> giải quyết f():
... excs = [OSError('error 1'), SystemError('error 2')]
... raise ExceptionGroup('có vấn đề', exs)
...
>>> f()
  + Nhóm ngoại lệ Traceback (cuộc gọi gần đây nhất):
  |   Tệp "<stdin>", dòng 1, trong <module>
  |     f()
  |     ~ ^^
  |   Tệp "<stdin>", dòng 3, trong f
  |     raise ExceptionGroup('có vấn đề', exs)
  | Nhóm ngoại lệ: đã xảy ra sự cố (2 ngoại lệ phụ)
  +-+---------------- 1 ----------------
    | Lỗi hệ điều hành: lỗi 1
    +---------------- 2 ----------------
    | Lỗi hệ thống: lỗi 2
    +-----------------------------------
>>> thử:
... f()
... ngoại trừ Ngoại lệ  e:
... print(f'caught {type(e)}: {e}')
...
bị bắt <class 'ExceptionGroup'>: đã xảy ra sự cố (2 ngoại lệ phụ)
>>>

Bằng cách sử dụng except* thay vì except, chúng tôi chỉ có thể xử lý có chọn lọc các trường hợp ngoại lệ trong nhóm khớp với một loại nhất định. Trong ví dụ sau, hiển thị một nhóm ngoại lệ lồng nhau, mỗi mệnh đề except* trích xuất từ ​​các ngoại lệ nhóm thuộc một loại nhất định trong khi cho phép tất cả các ngoại lệ khác lan truyền sang các mệnh đề khác và cuối cùng được đưa ra lại.

>>> giải quyết f():
... nâng cao ExceptionGroup(
... "nhóm1",
... [
... OSError(1),
... SystemError(2),
... Nhóm ngoại lệ(
... "nhóm2",
... [
... OSError(3),
... Lỗi đệ quy(4)
...]
... )
...]
... )
...
>>> thử:
... f()
... ngoại trừ* OSError as e:
... print("Có lỗi OS")
... ngoại trừ* SystemError as e:
... print("Có lỗi hệ thống")
...
Đã có lỗi hệ điều hành
Đã có lỗi hệ thống
  + Nhóm ngoại lệ Traceback (cuộc gọi gần đây nhất):
  |   Tệp "<stdin>", dòng 2, trong <module>
  |     f()
  |     ~ ^^
  |   Tệp "<stdin>", dòng 2, trong f
  |     tăng Nhóm ngoại lệ (
  |     ...<12 dòng>...
  |     )
  | Nhóm ngoại lệ: nhóm1 (1 ngoại lệ phụ)
  +-+---------------- 1 ----------------
    | Nhóm ngoại lệ: nhóm2 (1 ngoại lệ phụ)
    +-+---------------- 1 ----------------
      | Lỗi đệ quy: 4
      +-----------------------------------
>>>

Lưu ý rằng các ngoại lệ được lồng trong một nhóm ngoại lệ phải là các thể hiện chứ không phải kiểu. Điều này là do trong thực tế, các trường hợp ngoại lệ thường là những trường hợp đã được chương trình đưa ra và phát hiện theo mẫu sau:

>>> exs = []
... để kiểm tra trong các bài kiểm tra:
... thử:
... test.run()
... ngoại trừ Ngoại lệ  e:
... exes.append(e)
...
>>> nếu ngoại trừ:
... raise ExceptionGroup("Lỗi kiểm tra", exs)
...

8.10. Làm phong phú các ngoại lệ bằng ghi chú

Khi một ngoại lệ được tạo ra để đưa ra, nó thường được khởi tạo với thông tin mô tả lỗi đã xảy ra. Có những trường hợp việc thêm thông tin sau khi ngoại lệ bị phát hiện sẽ rất hữu ích. Với mục đích này, các ngoại lệ có một phương thức add_note(note) chấp nhận một chuỗi và thêm nó vào danh sách ghi chú của ngoại lệ. Kết xuất truy nguyên tiêu chuẩn bao gồm tất cả các ghi chú, theo thứ tự chúng được thêm vào, sau ngoại lệ.

>>> thử:
... raise TypeError('bad type')
... ngoại trừ Ngoại lệ  e:
... e.add_note('Thêm một số thông tin')
... e.add_note('Thêm một số thông tin khác')
... nâng cao
...
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 2, trong <module>
    tăng TypeError('loại xấu')
LoạiLỗi: loại xấu
Thêm một số thông tin
Thêm một số thông tin khác
>>>

Ví dụ: khi thu thập các ngoại lệ vào một nhóm ngoại lệ, chúng tôi có thể muốn thêm thông tin ngữ cảnh cho từng lỗi riêng lẻ. Trong phần sau đây, mỗi ngoại lệ trong nhóm đều có ghi chú cho biết thời điểm xảy ra lỗi này.

>>> giải quyết f():
... nâng cao OSError('thao tác thất bại')
...
>>> exs = []
>>> cho i trong phạm vi (3):
... thử:
... f()
... ngoại trừ Ngoại lệ  e:
... e.add_note(f'Đã xảy ra trong lần lặp {i+1}')
... exes.append(e)
...
>>> raise ExceptionGroup('Chúng tôi gặp một số vấn đề', exs)
  + Nhóm ngoại lệ Traceback (cuộc gọi gần đây nhất):
  |   Tệp "<stdin>", dòng 1, trong <module>
  |     raise ExceptionGroup('Chúng tôi gặp một số vấn đề', exs)
  | Nhóm ngoại lệ: Chúng tôi gặp một số vấn đề (3 ngoại lệ phụ)
  +-+---------------- 1 ----------------
    | Traceback (cuộc gọi gần đây nhất):
    |   Tệp "<stdin>", dòng 3, trong <module>
    |     f()
    |     ~ ^^
    |   Tệp "<stdin>", dòng 2, trong f
    |     nâng cao OSError('thao tác thất bại')
    | OSError: thao tác không thành công
    | Đã xảy ra ở lần lặp 1
    +---------------- 2 ----------------
    | Traceback (cuộc gọi gần đây nhất):
    |   Tệp "<stdin>", dòng 3, trong <module>
    |     f()
    |     ~ ^^
    |   Tệp "<stdin>", dòng 2, trong f
    |     nâng cao OSError('thao tác thất bại')
    | OSError: thao tác không thành công
    | Đã xảy ra ở lần lặp 2
    +---------------- 3 ----------------
    | Traceback (cuộc gọi gần đây nhất):
    |   Tệp "<stdin>", dòng 3, trong <module>
    |     f()
    |     ~ ^^
    |   Tệp "<stdin>", dòng 2, trong f
    |     nâng cao OSError('thao tác thất bại')
    | OSError: thao tác không thành công
    | Đã xảy ra ở lần lặp 3
    +-----------------------------------
>>>