Làm cách nào để nâng cao Ngoại lệ tương tự với thông báo tùy chỉnh trong Python?


145

Tôi có trykhối này trong mã của tôi:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise ValueError(errmsg)

Nói đúng ra, tôi thực sự đang nuôi một người khác ValueError , không phải người ValueErrorbị ném do_something...(), được gọi là errtrong trường hợp này. Làm cách nào để đính kèm một tin nhắn tùy chỉnh err? Tôi đã thử đoạn mã sau nhưng không thành công do err, ValueError chẳng hạn , không thể gọi được:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)

13
@ Hamish, đính kèm thông tin bổ sung và nâng cao ngoại lệ có thể rất hữu ích khi gỡ lỗi.
Johan Lundberg

@Johan Hoàn toàn - và đó là những gì một stacktrace dành cho. Không thể hiểu tại sao bạn chỉnh sửa thông báo lỗi hiện tại thay vì đưa ra một lỗi mới.
Hamish

@ Hamish. Chắc chắn nhưng bạn có thể thêm những thứ khác. Đối với câu hỏi của bạn, hãy xem câu trả lời của tôi và ví dụ về UnicodeDecodeError. Nếu bạn có ý kiến ​​về điều đó có lẽ bình luận câu trả lời của tôi thay vào đó.
Johan Lundberg


1
@Kit là năm 2020 và python 3 có ở khắp mọi nơi. Tại sao không thay đổi câu trả lời được chấp nhận cho câu trả lời của Ben :-)
mit

Câu trả lời:


88

Cập nhật: Đối với Python 3, hãy kiểm tra câu trả lời của Ben


Để đính kèm một thông báo vào ngoại lệ hiện tại và nâng lại nó: (lần thử bên ngoài / ngoại trừ chỉ để hiển thị hiệu ứng)

Đối với trăn 2.x trong đó x> = 6:

try:
    try:
      raise ValueError  # something bad...
    except ValueError as err:
      err.message=err.message+" hello"
      raise              # re-raise current exception
except ValueError as e:
    print(" got error of type "+ str(type(e))+" with message " +e.message)

Điều này cũng sẽ làm điều đúng nếu errnguồn gốc từ ValueError. Ví dụUnicodeDecodeError .

Lưu ý rằng bạn có thể thêm bất cứ điều gì bạn muốn err. Ví dụ err.problematic_array=[1,2,3].


Chỉnh sửa: @Ducan điểm trong một nhận xét ở trên không hoạt động với python 3 vì .messagekhông phải là thành viên của ValueError. Thay vào đó, bạn có thể sử dụng điều này (python hợp lệ 2.6 trở lên hoặc 3.x):

try:
    try:
      raise ValueError
    except ValueError as err:
       if not err.args: 
           err.args=('',)
       err.args = err.args + ("hello",)
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e.args))

Chỉnh sửa2:

Tùy thuộc vào mục đích là gì, bạn cũng có thể chọn thêm thông tin bổ sung dưới tên biến của riêng bạn. Đối với cả python2 và python3:

try:
    try:
      raise ValueError
    except ValueError as err:
       err.extra_info = "hello"
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e))
    if 'extra_info' in dir(e):
       print e.extra_info

9
Vì bạn đã nỗ lực sử dụng xử lý ngoại lệ kiểu Python 3 và printcó lẽ bạn nên lưu ý rằng mã của bạn không hoạt động trong Python 3.x vì không có messagethuộc tính về ngoại lệ. err.args = (err.args[0] + " hello",) + err.args[1:]có thể hoạt động đáng tin cậy hơn (và sau đó chỉ cần chuyển đổi thành một chuỗi để nhận thông báo).
Duncan

1
Thật không may, không có gì đảm bảo rằng arg [0] là một kiểu chuỗi biểu thị một thông báo lỗi - "Bộ các đối số được đưa ra cho hàm tạo ngoại lệ. Một số trường hợp ngoại lệ tích hợp (như IOError) mong đợi một số lượng đối số nhất định và gán một ý nghĩa đặc biệt cho các phần tử của bộ này, trong khi các phần tử khác thường chỉ được gọi với một chuỗi duy nhất đưa ra thông báo lỗi. ". Vì vậy, mã sẽ không hoạt động arg [0] không phải là một thông báo lỗi (nó có thể là một int hoặc nó có thể là một chuỗi đại diện cho một tên tệp).
Trent

1
@Tara, Thú vị. Bạn có một tài liệu tham khảo về điều đó? Sau đó, tôi sẽ thêm vào một thành viên hoàn toàn mới: err.my_own_extra_info. Hoặc gói gọn tất cả trong ngoại lệ của riêng tôi giữ thông tin mới và thông tin gốc.
Johan Lundberg

2
Một ví dụ thực tế khi args [0] không phải là thông báo lỗi - docs.python.org/2/l Library / exceptions.html - "ngoại lệ Môi trườngError Lớp cơ sở cho các ngoại lệ có thể xảy ra bên ngoài hệ thống Python: IOError, OSError. Khi các ngoại lệ của loại này được tạo bằng 2 tuple, mục đầu tiên có sẵn trên thuộc tính errno của thể hiện (nó được coi là một số lỗi) và mục thứ hai có sẵn trên thuộc tính strerror (thường là liên kết thông báo lỗi). Bản thân bộ tuple cũng có sẵn trên thuộc tính args. "
Trent

2
Tôi không hiểu điều này cả. Lý do duy nhất để thiết lập .messagethuộc tính làm bất cứ điều gì ở đây là thuộc tính này được in rõ ràng . Nếu bạn đưa ra ngoại lệ mà không bắt và in, bạn sẽ không thấy .messagethuộc tính làm bất cứ điều gì hữu ích.
DanielSank

170

Nếu bạn đủ may mắn để chỉ hỗ trợ python 3.x, điều này thực sự trở thành một điều tuyệt vời :)

tăng từ

Chúng ta có thể xâu chuỗi các trường hợp ngoại lệ bằng cách tăng từ .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks') from e

Trong trường hợp này, ngoại lệ mà người gọi của bạn sẽ bắt gặp có số dòng của nơi chúng tôi nêu ra ngoại lệ của mình.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

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

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks') from e
Exception: Smelly socks

Lưu ý ngoại lệ dưới cùng chỉ có stacktrace từ nơi chúng ta nêu ra ngoại lệ của mình. Người gọi của bạn vẫn có thể nhận được ngoại lệ ban đầu bằng cách truy cập __cause__thuộc tính của ngoại lệ họ bắt được.

with_tracBack

Hoặc bạn có thể sử dụng with_tracBack .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks').with_traceback(e.__traceback__)

Sử dụng biểu mẫu này, ngoại lệ mà người gọi của bạn sẽ bắt gặp có dấu vết từ nơi xảy ra lỗi ban đầu.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks').with_traceback(e.__traceback__)
  File "test.py", line 2, in <module>
    1 / 0
Exception: Smelly socks

Lưu ý ngoại lệ dưới cùng có dòng nơi chúng tôi thực hiện phép chia không hợp lệ cũng như dòng nơi chúng tôi lấy lại ngoại lệ.


1
Có thể thêm một thông điệp tùy chỉnh vào một ngoại lệ mà không cần thêm dấu vết? Ví dụ: có thể raise Exception('Smelly socks') from eđược sửa đổi để chỉ thêm "Vớ có mùi" như một nhận xét cho lần truy xuất ban đầu thay vì giới thiệu một lần truy xuất mới của chính nó.
joelostblom

Đó là hành vi bạn sẽ nhận được từ câu trả lời của Johan Lundberg
Ben

3
Điều này thực sự đáng yêu. Cảm ơn bạn.
allanberry

3
Nâng cao một ngoại lệ mới hoặc tăng ngoại lệ chuỗi với các thông báo mới tạo ra nhiều nhầm lẫn hơn mức cần thiết trong nhiều trường hợp. Chính nó ngoại lệ là phức tạp để xử lý. Chiến lược tốt hơn là chỉ thêm thông điệp của bạn vào đối số của ngoại lệ ban đầu nếu có thể như trong err.args + = ("message",) và nâng lại thông báo ngoại lệ. Truy nguyên có thể không đưa bạn đến các số dòng nơi bắt ngoại lệ nhưng chắc chắn nó sẽ đưa bạn đến nơi xảy ra ngoại lệ.
người dùng dấu hoa thị

2
Bạn cũng có thể triệt tiêu rõ ràng việc hiển thị chuỗi ngoại lệ bằng cách chỉ định Không có trong mệnh đề from:raise RuntimeError("Something bad happened") from None
pfabri

10
try:
    try:
        int('a')
    except ValueError as e:
        raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
    print err

in:

There is a problem: invalid literal for int() with base 10: 'a'

1
Tôi đã tự hỏi nếu có một thành ngữ Python cho những gì tôi đang cố gắng làm, ngoài việc đưa ra một ví dụ khác .
Bộ

@Kit - Tôi sẽ gọi nó là 'tái huy động một ngoại lệ': docs.python.org/reference/simple_stmts.html#raise
eumiro

1
@eumiro, Không có bạn đang tạo một ngoại lệ mới. Xem câu trả lời của tôi. Từ liên kết của bạn: "... nhưng tăng không có biểu thức nên được ưu tiên nếu ngoại lệ được tăng lại là ngoại lệ hoạt động gần đây nhất trong phạm vi hiện tại."
Johan Lundberg

3
@JohanLundberg - raisekhông có tham số đang tăng lại. Nếu OP muốn thêm một tin nhắn, anh ta phải đưa ra một ngoại lệ mới và có thể sử dụng lại tin nhắn / loại ngoại lệ ban đầu.
eumiro

2
Nếu bạn muốn thêm một tin nhắn, bạn không thể tạo một tin nhắn mới từ đầu bằng cách ném "ValueError". Bằng cách đó, bạn hủy thông tin cơ bản của loại ValueError đó (tương tự như cắt trong C ++). Bằng cách ném lại ngoại lệ tương tự với tăng mà không có đối số, bạn chuyển đối tượng ban đầu với loại cụ thể chính xác đó (xuất phát từ ValueError).
Johan Lundberg

9

Có vẻ như tất cả các câu trả lời đang thêm thông tin vào e.args [0], do đó thay đổi thông báo lỗi hiện có. Thay vào đó, có một nhược điểm để mở rộng các tuple args? Tôi nghĩ rằng nhược điểm có thể là, bạn có thể để lại thông báo lỗi ban đầu cho các trường hợp cần phân tích chuỗi đó; và bạn có thể thêm nhiều yếu tố vào bộ dữ liệu nếu việc xử lý lỗi tùy chỉnh của bạn tạo ra một số thông báo hoặc mã lỗi, trong trường hợp truy nguyên sẽ được phân tích cú pháp theo chương trình (như thông qua công cụ giám sát hệ thống).

## Approach #1, if the exception may not be derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args = (e.args if e.args else tuple()) + ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

hoặc là

## Approach #2, if the exception is always derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args += ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

Bạn có thể thấy một nhược điểm của phương pháp này?


Câu trả lời cũ hơn của tôi không làm thay đổi e.args [0].
Johan Lundberg

4

Mẫu mã này sẽ cho phép bạn đưa ra một ngoại lệ với một thông điệp tùy chỉnh.

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")

3
Điều này không bảo tồn dấu vết ngăn xếp.
plok

Người hỏi không chỉ định rằng dấu vết ngăn xếp được bảo tồn.
shrewmouse

4

Hoặc đưa ra ngoại lệ mới với thông báo lỗi của bạn bằng cách sử dụng

raise Exception('your error message')

hoặc là

raise ValueError('your error message')

trong nơi bạn muốn nâng nó HOẶC đính kèm (thay thế) thông báo lỗi vào ngoại lệ hiện tại bằng cách sử dụng 'từ' (chỉ hỗ trợ Python 3.x):

except ValueError as e:
  raise ValueError('your message') from e

Thanx, @gberger, 'từ phương pháp e' thực sự không được hỗ trợ bởi python 2.x
Alexey Antonenko

3

Đây là chức năng tôi sử dụng để sửa đổi thông báo ngoại lệ trong Python 2.7 và 3.x trong khi duy trì truy nguyên gốc. Nó yêu cầusix

def reraise_modify(caught_exc, append_msg, prepend=False):
    """Append message to exception while preserving attributes.

    Preserves exception class, and exception traceback.

    Note:
        This function needs to be called inside an except because
        `sys.exc_info()` requires the exception context.

    Args:
        caught_exc(Exception): The caught exception object
        append_msg(str): The message to append to the caught exception
        prepend(bool): If True prepend the message to args instead of appending

    Returns:
        None

    Side Effects:
        Re-raises the exception with the preserved data / trace but
        modified message
    """
    ExceptClass = type(caught_exc)
    # Keep old traceback
    traceback = sys.exc_info()[2]
    if not caught_exc.args:
        # If no args, create our own tuple
        arg_list = [append_msg]
    else:
        # Take the last arg
        # If it is a string
        # append your message.
        # Otherwise append it to the
        # arg list(Not as pretty)
        arg_list = list(caught_exc.args[:-1])
        last_arg = caught_exc.args[-1]
        if isinstance(last_arg, str):
            if prepend:
                arg_list.append(append_msg + last_arg)
            else:
                arg_list.append(last_arg + append_msg)
        else:
            arg_list += [last_arg, append_msg]
    caught_exc.args = tuple(arg_list)
    six.reraise(ExceptClass,
                caught_exc,
                traceback)

3

Các ngoại lệ dựng sẵn của Python 3 có strerrortrường:

except ValueError as err:
  err.strerror = "New error message"
  raise err

Điều này dường như không hoạt động. Bạn đang mất tích đôi khi?
MasayoMusic

2

Câu trả lời hiện tại không hoạt động tốt với tôi, nếu ngoại lệ không được bắt lại, thông báo được nối thêm sẽ không được hiển thị.

Nhưng làm như dưới đây cả hai giữ dấu vết và hiển thị thông báo được nối thêm bất kể ngoại lệ có được bắt lại hay không.

try:
  raise ValueError("Original message")
except ValueError as err:
  t, v, tb = sys.exc_info()
  raise t, ValueError(err.message + " Appended Info"), tb

(Tôi đã sử dụng Python 2.7, chưa thử nó trong Python 3)


1

Không có giải pháp nào ở trên thực hiện chính xác những gì tôi muốn, đó là thêm một số thông tin vào phần đầu tiên của thông báo lỗi, tức là tôi muốn người dùng của mình nhìn thấy thông điệp tùy chỉnh của tôi trước tiên.

Điều này làm việc cho tôi:

exception_raised = False
try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    message = str(e)
    exception_raised = True

if exception_raised:
    message_to_prepend = "Custom text"
    raise ValueError(message_to_prepend + message)

0

Điều này chỉ hoạt động với Python 3 . Bạn có thể sửa đổi các đối số ban đầu của ngoại lệ và thêm các đối số của riêng bạn.

Một ngoại lệ ghi nhớ các đối số mà nó được tạo ra. Tôi đoán điều này là để bạn có thể sửa đổi ngoại lệ.

Trong hàm, reraisechúng tôi bổ sung các đối số ban đầu của ngoại lệ với bất kỳ đối số mới nào chúng tôi muốn (như tin nhắn). Cuối cùng, chúng tôi nêu lại ngoại lệ trong khi bảo tồn lịch sử theo dõi.

def reraise(e, *args):
  '''re-raise an exception with extra arguments
  :param e: The exception to reraise
  :param args: Extra args to add to the exception
  '''

  # e.args is a tuple of arguments that the exception with instantiated with.
  #
  e.args = args + e.args

  # Recreate the expection and preserve the traceback info so thta we can see 
  # where this exception originated.
  #
  raise e.with_traceback(e.__traceback__)   


def bad():
  raise ValueError('bad')

def very():
  try:
    bad()
  except Exception as e:
    reraise(e, 'very')

def very_very():
  try:
    very()
  except Exception as e:
    reraise(e, 'very')

very_very()

đầu ra

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    very_very()
  File "main.py", line 30, in very_very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 28, in very_very
    very()
  File "main.py", line 24, in very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 22, in very
    bad()
  File "main.py", line 18, in bad
    raise ValueError('bad')
ValueError: ('very', 'very', 'bad')

-3

nếu bạn muốn tùy chỉnh loại lỗi, một điều đơn giản bạn có thể làm là xác định một lớp lỗi dựa trên ValueError.


Làm thế nào điều đó sẽ giúp trong trường hợp này?
Johan Lundberg
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.