Trích xuất thông tin theo dõi từ một đối tượng ngoại lệ


111

Với một đối tượng Ngoại lệ (không rõ nguồn gốc), có cách nào để lấy lại dấu vết của nó không? Tôi có mã như thế này:

def stuff():
   try:
       .....
       return useful
   except Exception as e:
       return e

result = stuff()
if isinstance(result, Exception):
    result.traceback <-- How?

Làm cách nào để trích xuất truy nguyên từ đối tượng Ngoại lệ khi tôi có nó?

Câu trả lời:


92

Câu trả lời cho câu hỏi này phụ thuộc vào phiên bản Python bạn đang sử dụng.

Trong Python 3

Thật đơn giản: các trường hợp ngoại lệ được trang bị một __traceback__thuộc tính có chứa dấu vết. Thuộc tính này cũng có thể ghi và có thể được thiết lập thuận tiện bằng cách sử dụng with_tracebackphương thức ngoại lệ:

raise Exception("foo occurred").with_traceback(tracebackobj)

Các tính năng này được mô tả tối thiểu như một phần của raisetài liệu.

Tất cả tín dụng cho phần này của câu trả lời sẽ thuộc về Vyctor, người đầu tiên đăng thông tin này . Tôi chỉ đưa nó vào đây vì câu trả lời này bị kẹt ở trên cùng và Python 3 đang trở nên phổ biến hơn.

Trong Python 2

Nó phức tạp một cách khó chịu. Rắc rối với tracebacks là chúng có các tham chiếu đến các khung ngăn xếp và các khung ngăn xếp có các tham chiếu đến các truy nguyên có tham chiếu đến các khung ngăn xếp có tham chiếu đến ... bạn có thể hiểu được. Điều này gây ra vấn đề cho bộ thu gom rác. (Cảm ơn ecatmur vì lần đầu tiên chỉ ra điều này.)

Cách tốt nhất để giải quyết vấn đề này là phá vỡ chu kỳ sau khi rời khỏi exceptmệnh đề, đó là những gì Python 3 làm. Giải pháp Python 2 xấu hơn nhiều: bạn được cung cấp một hàm đặc biệt sys.exc_info(), chỉ hoạt động bên trong except mệnh đề . Nó trả về một bộ giá trị chứa ngoại lệ, kiểu ngoại lệ và truy xuất cho bất kỳ ngoại lệ nào hiện đang được xử lý.

Vì vậy, nếu bạn đang ở bên trong exceptmệnh đề, bạn có thể sử dụng đầu ra của sys.exc_info()cùng với tracebackmô-đun để làm những việc hữu ích khác nhau:

>>> import sys, traceback
>>> def raise_exception():
...     try:
...         raise Exception
...     except Exception:
...         ex_type, ex, tb = sys.exc_info()
...         traceback.print_tb(tb)
...     finally:
...         del tb
... 
>>> raise_exception()
  File "<stdin>", line 3, in raise_exception

Nhưng khi chỉnh sửa của bạn cho thấy, bạn đang cố gắng để có được những traceback rằng sẽ đã được in nếu ngoại trừ của bạn đã không được xử lý, sau khi nó đã đã được xử lý. Đó là một câu hỏi khó hơn nhiều. Thật không may, sys.exc_infotrả về (None, None, None)khi không có ngoại lệ nào được xử lý. Các systhuộc tính liên quan khác cũng không giúp được gì. sys.exc_tracebackkhông được chấp nhận và không được xác định khi không có ngoại lệ nào được xử lý; sys.last_tracebackcó vẻ hoàn hảo, nhưng nó dường như chỉ được xác định trong các phiên tương tác.

Nếu bạn có thể kiểm soát cách đặt ngoại lệ, bạn có thể sử dụng inspectvà một ngoại lệ tùy chỉnh để lưu trữ một số thông tin. Nhưng tôi không hoàn toàn chắc chắn điều đó sẽ hoạt động như thế nào.

Nói thật, việc bắt và trả lại một ngoại lệ là một việc không bình thường. Đây có thể là một dấu hiệu cho thấy bạn cần phải cấu trúc lại.


Tôi đồng ý rằng việc trả lại các ngoại lệ theo cách nào đó là không bình thường, nhưng hãy xem câu hỏi khác của tôi để biết một số lý do đằng sau điều này.
georg

@ thg435, ok, điều này có ý nghĩa hơn. Hãy xem xét giải pháp ở trên của tôi bằng cách sử dụng sys.exc_infokết hợp với phương pháp gọi lại mà tôi đề xuất cho câu hỏi khác của bạn.
gửi


69

Kể từ Python 3.0 [PEP 3109] , lớp được xây dựng trong Exceptioncó một __traceback__thuộc tính chứa một traceback object(với Python 3.2.3):

>>> try:
...     raise Exception()
... except Exception as e:
...     tb = e.__traceback__
...
>>> tb
<traceback object at 0x00000000022A9208>

Vấn đề là sau khi Google__traceback__ một thời gian, tôi chỉ tìm thấy một số bài báo nhưng không có bài nào mô tả liệu bạn có nên (không) sử dụng hay không __traceback__.

Tuy nhiên, tài liệu Python 3 choraise biết rằng:

Một đối tượng theo dõi lại thường được tạo tự động khi một ngoại lệ được nâng lên và gắn vào nó dưới dạng __traceback__thuộc tính, có thể ghi được.

Vì vậy, tôi cho rằng nó có nghĩa là để được sử dụng.


4
Có, nó có nghĩa là để được sử dụng. Từ Có gì mới Trong Python 3.0 "PEP 3134: Ngoại lệ đối tượng tại lưu trữ traceback của họ như traceback thuộc tính Điều này có nghĩa rằng một đối tượng ngoại lệ bây giờ chứa tất cả các thông tin liên quan đến một ngoại lệ, và có rất ít lý do để sử dụng sys.exc_info () (. mặc dù sau này không bị loại bỏ). "
Maciej Szpakowski

Tôi thực sự không hiểu tại sao câu trả lời này lại do dự và phiến diện. Đó là một tài sản được lập thành văn bản; tại sao nó không phải là "có nghĩa là để được sử dụng"?
Mark Amery

2
@MarkAmery Có thể tên __trong tên chỉ ra rằng đó là chi tiết triển khai, không phải thuộc tính công cộng?
Cơ bản

4
@Basic đó không phải là những gì nó chỉ ra ở đây. Thông thường trong Python __foolà một phương thức riêng tư nhưng __foo__(cũng có dấu gạch dưới ở cuối) là một phương thức "ma thuật" (và không riêng tư).
Mark Amery

1
FYI, __traceback__thuộc tính an toàn 100% để sử dụng theo cách bạn muốn, không có GC. Thật khó để nói điều đó từ tài liệu, nhưng ecatmur đã tìm thấy bằng chứng khó khăn .
gửi

38

Một cách để lấy lại dấu vết dưới dạng một chuỗi từ một đối tượng ngoại lệ trong Python 3:

import traceback

# `e` is an exception object that you get from somewhere
traceback_str = ''.join(traceback.format_tb(e.__traceback__))

traceback.format_tb(...)trả về một danh sách các chuỗi. ''.join(...)kết hợp chúng với nhau. Để tham khảo thêm, vui lòng truy cập: https://docs.python.org/3/library/traceback.html#traceback.format_tb


21

Ngoài ra, nếu bạn muốn thực sự nhận được toàn bộ dấu vết như bạn sẽ thấy nó được in vào thiết bị đầu cuối của mình, bạn cần điều này:

>>> try:
...     print(1/0)
... except Exception as e:
...     exc = e
...
>>> exc
ZeroDivisionError('division by zero')
>>> tb_str = traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__)
>>> tb_str
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: division by zero\n']
>>> print("".join(tb_str))
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

Nếu bạn sử dụng format_tbnhư các câu trả lời ở trên, bạn sẽ nhận được ít thông tin hơn:

>>> tb_str = "".join(traceback.format_tb(exc.__traceback__))
>>> print("".join(tb_str))
  File "<stdin>", line 2, in <module>

4
Cuối cùng! Đây phải là câu trả lời hàng đầu. Cảm ơn bạn, Daniel!
Dany

3
Argh, tôi đã dành 20 phút qua để cố gắng tìm ra điều này trước khi tôi tìm thấy điều này :-) etype=type(exc)bây giờ có thể được bỏ qua btw: "Đã thay đổi trong phiên bản 3.5: Đối số kiểu etype bị bỏ qua và được suy ra từ loại giá trị." docs.python.org/3.7/library/… Đã thử nghiệm bằng Python 3.7.3.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

8

Có một lý do rất chính đáng để truy xuất nguồn gốc không được lưu trữ trong ngoại lệ; bởi vì theo dõi lưu giữ các tham chiếu đến các địa phương của ngăn xếp của nó, điều này sẽ dẫn đến tham chiếu vòng tròn và rò rỉ bộ nhớ (tạm thời) cho đến khi GC hình tròn khởi động. (Đây là lý do tại sao bạn không bao giờ nên lưu trữ truy xuất trong một biến cục bộ .)

Về điều duy nhất tôi có thể nghĩ đến là bạn có thể tìm stuffcác hình cầu của Monkeypatch để khi nó nghĩ rằng nó đang bắt, Exceptionnó thực sự bắt một loại chuyên biệt và ngoại lệ sẽ truyền cho bạn với tư cách là người gọi:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
    stuff()
except Exception:
    import sys
    print sys.exc_info()

7
Cái này sai. Python 3 không đặt đối tượng theo dõi lại trong ngoại lệ, như e.__traceback__.
Glenn Maynard

6
@GlennMaynard Python 3 giải quyết vấn đề bằng cách xóa mục tiêu ngoại lệ khi thoát khỏi exceptkhối, theo PEP 3110 .
ecatmur 17/12/12
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.