Thêm thông tin vào một ngoại lệ?


142

Tôi muốn đạt được một cái gì đó như thế này:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

Nhưng những gì tôi nhận được là:

Traceback..
  IOError('Stuff')

Bất kỳ manh mối như làm thế nào để đạt được điều này? Làm thế nào để làm điều đó cả trong Python 2 và 3?


Trong khi tìm tài liệu cho messagethuộc tính Exception, tôi đã tìm thấy câu hỏi SO này, BaseException.message không dùng nữa trong Python 2.6 , điều này dường như cho thấy việc sử dụng nó hiện không được khuyến khích (và tại sao nó không có trong tài liệu).
martineau

thật đáng buồn, liên kết đó dường như không hoạt động nữa.
Michael Scott Cuthbert

1
@MichaelScottCuthbert đây là một lựa chọn tốt: itmaybeahack.com/book/python-2.6/html/p02/ mẹo
Niels Keurentjes

Đây là một lời giải thích thực sự tốt về trạng thái của thuộc tính thông báo và mối quan hệ của nó với thuộc tính args và PEP 352 . Đó là từ cuốn sách miễn phí Kỹ năng xây dựng trong Python của Steven F. Lott.
martineau

Câu trả lời:


118

Tôi sẽ làm như thế này để thay đổi loại của nó foo()sẽ không yêu cầu thay đổi nó trong bar().

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

Cập nhật 1

Đây là một sửa đổi nhỏ để duy trì truy nguyên gốc:

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

Cập nhật 2

Đối với Python 3.x, mã trong bản cập nhật đầu tiên của tôi về mặt cú pháp không chính xác cộng với ý tưởng có một messagethuộc tính trên BaseExceptionđược rút lại trong một thay đổi thành PEP 352 vào ngày 2012-05-16 (bản cập nhật đầu tiên của tôi đã được đăng vào ngày 2012 / 03-12) . Vì vậy, hiện tại, trong Python 3.5.2, bạn cần phải làm gì đó dọc theo các dòng này để duy trì truy nguyên và không mã hóa loại ngoại lệ trong hàm bar(). Cũng lưu ý rằng sẽ có dòng:

During handling of the above exception, another exception occurred:

trong các thông báo truy nguyên được hiển thị.

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

Cập nhật 3

Một commenter hỏi nếu có một cách mà có thể làm việc trong cả hai Python 2 và 3. Mặc dù câu trả lời có vẻ là "Không" do sự khác biệt cú pháp, có một con đường xung quanh đó bằng cách sử dụng một hàm helper như reraise()trongsix Add- trên mô-đun. Vì vậy, nếu bạn không muốn sử dụng thư viện vì một số lý do, dưới đây là phiên bản độc lập đơn giản hóa.

Lưu ý rằng, vì ngoại lệ được phát lại trong reraise()hàm, nó sẽ xuất hiện trong bất kỳ dấu vết nào được đưa ra, nhưng kết quả cuối cùng là những gì bạn muốn.

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')

3
Điều đó làm mất đi nền tảng, loại đánh bại điểm thêm thông tin vào một ngoại lệ hiện có. Ngoài ra, nó không hoạt động ngoại lệ với ctor có> 1 đối số (loại là thứ bạn không thể kiểm soát từ nơi bạn bắt ngoại lệ).
Václav Slavík

1
@ Václav: Khá dễ dàng để tránh mất backtrace - như thể hiện trong bản cập nhật tôi đã thêm. Mặc dù điều này vẫn không xử lý mọi ngoại lệ có thể hiểu được, nhưng nó hoạt động cho các trường hợp tương tự như những gì được thể hiện trong câu hỏi của OP.
martineau

1
Điều này không hoàn toàn đúng. Nếu loại (e) ghi đè __str__, bạn có thể nhận được kết quả không mong muốn. Cũng lưu ý rằng đối số thứ hai được truyền cho hàm tạo được đưa ra bởi đối số thứ nhất, điều này mang lại một chút vô nghĩa type(e)(type(e)(e.message). Thứ ba, e.message không được ủng hộ cho e.args [0].
bukzor

1
Vì vậy, không có cách di động nào hoạt động trong cả Python 2 và 3?
Elias Dornele

1
@martineau Mục đích của việc nhập bên trong khối ngoại trừ là gì? Đây có phải là để tiết kiệm bộ nhớ bằng cách chỉ nhập khi cần thiết?
AllTradesJack

114

Trong trường hợp bạn đến đây để tìm kiếm một giải pháp cho Python 3 , hướng dẫn sử dụng cho biết:

Khi đưa ra một ngoại lệ mới (thay vì sử dụng trần raiseđể tăng lại ngoại lệ hiện đang được xử lý), bối cảnh ngoại lệ ngầm có thể được bổ sung bằng một nguyên nhân rõ ràng bằng cách sử dụng từ tăng:

raise new_exc from original_exc

Thí dụ:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

Trông giống như thế này cuối cùng:

2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

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

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

Biến một bản hoàn toàn không cần thiết TypeErrorthành một thông điệp tốt đẹp với gợi ý về một giải pháp mà không làm rối tung Ngoại lệ ban đầu.


14
Đây là giải pháp tốt nhất, vì các điểm ngoại lệ kết quả trở lại nguyên nhân ban đầu, cung cấp chi tiết hơn.
JT

Có giải pháp nào để chúng ta có thể thêm một số tin nhắn nhưng vẫn không đưa ra một ngoại lệ mới? Tôi có nghĩa là chỉ cần mở rộng thông điệp của trường hợp ngoại lệ.
edcSam

Yaa ~ ~ nó hoạt động, nhưng nó cảm thấy như một việc không nên làm với tôi. Tin nhắn được lưu trữ e.args, nhưng đó là một Tuple, vì vậy nó không thể thay đổi. Vì vậy, trước tiên hãy sao chép argsvào một danh sách, sau đó sửa đổi nó, sau đó sao chép lại dưới dạng Tuple:args = list(e.args) args[0] = 'bar' e.args = tuple(args)
Chris

27

Giả sử bạn không muốn hoặc không thể sửa đổi foo (), bạn có thể làm điều này:

try:
    raise IOError('stuff')
except Exception as e:
    if len(e.args) >= 1:
        e.args = (e.args[0] + ' happens',) + e.args[1:]
    raise

Đây thực sự là giải pháp duy nhất ở đây giải quyết vấn đề trong Python 3 mà không có thông báo "Trong quá trình xử lý ngoại lệ trên, một ngoại lệ khác xảy ra".

Trong trường hợp đường tăng lại nên được thêm vào dấu vết ngăn xếp, viết raise ethay vì raisesẽ thực hiện thủ thuật.


Nhưng trong trường hợp này nếu ngoại lệ thay đổi trong foo, tôi cũng phải thay đổi thanh đúng không?
anijhaw

1
Nếu bạn bắt Ngoại lệ (đã chỉnh sửa ở trên), bạn có thể bắt bất kỳ ngoại lệ thư viện chuẩn nào (cũng như ngoại lệ từ Ngoại lệ và gọi Ngoại lệ .__ init__).
Steve Howard

6
để hoàn thiện hơn / hợp tác, bao gồm các phần khác của bộ tài liệu gốc:e.args = ('mynewstr' + e.args[0],) + e.args[1:]
Dubslow

1
@ nmz787 Đây là giải pháp tốt nhất cho Python 3 trên thực tế. Chính xác thì lỗi của bạn là gì?
Christian

1
@Dublow và martineau Tôi kết hợp các đề xuất của bạn vào một chỉnh sửa.
Christian

9

Tôi không thích tất cả các câu trả lời cho đến nay. Họ vẫn còn quá dài imho. Trong cả mã và thông báo đầu ra.

Tất cả những gì tôi muốn có là stacktrace trỏ đến ngoại lệ nguồn, không có ngoại lệ nào ở giữa, vì vậy không tạo ra ngoại lệ mới, chỉ cần nâng lại bản gốc với tất cả các trạng thái khung stack có liên quan trong đó, dẫn đến đó.

Steve Howard đã đưa ra một câu trả lời hay mà tôi muốn mở rộng, không, giảm ... chỉ với python 3.

except Exception as e:
    e.args = ("Some failure state", *e.args)
    raise

Điều mới duy nhất là việc mở rộng / giải nén tham số khiến nó đủ nhỏ và dễ sử dụng.

Thử nó:

foo = None

try:
    try:
        state = "bar"
        foo.append(state)

    except Exception as e:
        e.args = ("Appending '"+state+"' failed", *e.args)
        raise

    print(foo[0]) # would raise too

except Exception as e:
    e.args = ("print(foo) failed: " + str(foo), *e.args)
    raise

Điều này sẽ cung cấp cho bạn:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")

Một bản in đẹp đơn giản có thể là một cái gì đó như

print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))

5

Một cách tiếp cận tiện dụng mà tôi đã sử dụng là sử dụng thuộc tính lớp làm lưu trữ để biết chi tiết, vì thuộc tính lớp có thể truy cập được cả từ đối tượng lớp và thể hiện của lớp:

class CustomError(Exception):
    def __init__(self, details: Dict):
        self.details = details

Sau đó, trong mã của bạn:

raise CustomError({'data': 5})

Và khi bắt lỗi:

except CustomError as e:
    # Do whatever you want with the exception instance
    print(e.details)

Không thực sự hữu ích vì OP đang yêu cầu các chi tiết được in như một phần của dấu vết ngăn xếp khi ngoại lệ ban đầu được ném và không bị bắt.
cowbert

Tôi nghĩ rằng giải pháp là tốt. Nhưng mô tả không đúng sự thật. Các thuộc tính lớp được sao chép vào các thể hiện khi bạn khởi tạo chúng. Vì vậy, khi bạn sửa đổi "chi tiết" thuộc tính, ví dụ, thuộc tính lớp vẫn sẽ là Không có. Dù sao chúng tôi muốn hành vi này ở đây.
Adam Wallner

2

Không giống như câu trả lời trước, điều này hoạt động khi đối mặt với các ngoại lệ với thực sự xấu __str__. Nó làm thay đổi kiểu Tuy nhiên, để yếu tố ra vô ích __str__hiện thực.

Tôi vẫn muốn tìm một cải tiến bổ sung không sửa đổi loại.

from contextlib import contextmanager
@contextmanager
def helpful_info():
    try:
        yield
    except Exception as e:
        class CloneException(Exception): pass
        CloneException.__name__ = type(e).__name__
        CloneException.__module___ = type(e).__module__
        helpful_message = '%s\n\nhelpful info!' % e
        import sys
        raise CloneException, helpful_message, sys.exc_traceback


class BadException(Exception):
    def __str__(self):
        return 'wat.'

with helpful_info():
    raise BadException('fooooo')

Trac trở lại ban đầu và loại (tên) được bảo tồn.

Traceback (most recent call last):
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
  File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
    self.gen.throw(type, value, traceback)
  File "re_raise.py", line 5, in helpful_info
    yield
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
__main__.BadException: wat.

helpful info!

2

Tôi sẽ cung cấp một đoạn mã mà tôi thường sử dụng bất cứ khi nào tôi muốn thêm thông tin bổ sung vào một ngoại lệ. Tôi làm việc cả trong Python 2.7 và 3.6.

import sys
import traceback

try:
    a = 1
    b = 1j

    # The line below raises an exception because
    # we cannot compare int to complex.
    m = max(a, b)  

except Exception as ex:
    # I create my  informational message for debugging:
    msg = "a=%r, b=%r" % (a, b)

    # Gather the information from the original exception:
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Format the original exception for a nice printout:
    traceback_string = ''.join(traceback.format_exception(
        exc_type, exc_value, exc_traceback))

    # Re-raise a new exception of the same class as the original one, 
    # using my custom message and the original traceback:
    raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))

Đoạn mã trên cho kết quả đầu ra sau:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
     14     raise type(ex)(
     15         "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16         (msg, traceback_string))

TypeError: a=1, b=1j

ORIGINAL TRACEBACK:

Traceback (most recent call last):
  File "<ipython-input-6-09b74752c60d>", line 7, in <module>
    m = max(a, b)  # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers


Tôi biết điều này sai lệch một chút so với ví dụ được cung cấp trong câu hỏi, tuy nhiên tôi hy vọng ai đó thấy nó hữu ích.


1

Bạn có thể xác định ngoại lệ của riêng mình kế thừa từ cái khác và tạo hàm tạo của chính nó để đặt giá trị.

Ví dụ:

class MyError(Exception):
   def __init__(self, value):
     self.value = value
     Exception.__init__(self)

   def __str__(self):
     return repr(self.value)

2
Không giải quyết cần phải thay đổi / nối một cái gì đó vào messagengoại lệ ban đầu (nhưng tôi có thể sửa được).
martineau

-6

Có lẽ

except Exception as e:
    raise IOError(e.message + 'happens at %s'%arg1)
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.