Ngoại lệ bên trong có phải là gì (với trac trở lại) trong Python?


146

Nền tảng của tôi là về C # và tôi mới bắt đầu lập trình bằng Python. Khi một ngoại lệ được ném, tôi thường muốn bọc nó trong một ngoại lệ khác để thêm thông tin, trong khi vẫn hiển thị dấu vết ngăn xếp đầy đủ. Nó khá dễ dàng trong C #, nhưng làm thế nào để tôi làm điều đó trong Python?

Ví dụ. trong C # tôi sẽ làm một cái gì đó như thế này:

try
{
  ProcessFile(filePath);
}
catch (Exception ex)
{
  throw new ApplicationException("Failed to process file " + filePath, ex);
}

Trong Python tôi có thể làm một cái gì đó tương tự:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file ' + filePath, e)

... nhưng điều này làm mất đi dấu vết của ngoại lệ bên trong!

Chỉnh sửa: Tôi muốn xem cả tin nhắn ngoại lệ và cả dấu vết ngăn xếp và tương quan cả hai. Đó là, tôi muốn thấy trong đầu ra ngoại lệ X xảy ra ở đây và sau đó ngoại lệ Y ở đó - giống như tôi đã làm trong C #. Điều này có thể có trong Python 2.6 không? Có vẻ như điều tốt nhất tôi có thể làm cho đến nay (dựa trên câu trả lời của Glenn Maynard) là:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]

Điều này bao gồm cả tin nhắn và cả tracebacks, nhưng nó không hiển thị ngoại lệ nào xảy ra ở nơi truy xuất.


3
Câu trả lời được chấp nhận là hết hạn, có lẽ bạn nên xem xét chấp nhận một câu trả lời khác.
Aaron Hall

1
@AaronHall tiếc là OP đã không được nhìn thấy vào khoảng năm 2015.
Antti Haapala

Câu trả lời:


136

Con trăn 2

Thật đơn giản; vượt qua truy nguyên như là đối số thứ ba để nâng cao.

import sys
class MyException(Exception): pass

try:
    raise TypeError("test")
except TypeError, e:
    raise MyException(), None, sys.exc_info()[2]

Luôn luôn làm điều này khi bắt một ngoại lệ và nuôi lại một ngoại lệ khác.


4
Cảm ơn. Điều đó bảo tồn truy nguyên, nhưng nó làm mất thông báo lỗi của ngoại lệ ban đầu. Làm cách nào để xem cả tin nhắn và cả tracebacks?
EMP

6
raise MyException(str(e)), ..., v.v.
Glenn Maynard

23
Python 3 thêm raise E() from tb.with_traceback(...)
Dima Tisnek

3
@GlennMaynard đó là một câu hỏi khá cũ, nhưng đối số giữa của raisegiá trị là để chuyển sang ngoại lệ (trong trường hợp đối số đầu tiên là một lớp ngoại lệ và không phải là một thể hiện). Vì vậy, nếu bạn muốn trao đổi ngoại lệ, thay vì làm raise MyException(str(e)), None, sys.exc_info()[2], tốt hơn là sử dụng điều này : raise MyException, e.args, sys.exc_info()[2].
bgusach

8
Có thể sử dụng cách tuân thủ Python2 và 3 bằng cách sử dụng gói trong tương lai: python-future.org/compiverse_idioms.html#raising-exceptions Eg from future.utils import raise_raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda

239

Con trăn 3

Trong python 3 bạn có thể làm như sau:

try:
    raise MyExceptionToBeWrapped("I have twisted my ankle")

except MyExceptionToBeWrapped as e:

    raise MyWrapperException("I'm not in a good shape") from e

Điều này sẽ tạo ra một cái gì đó như thế này:

   Traceback (most recent call last):
   ...
   MyExceptionToBeWrapped: ("I have twisted my ankle")

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

   Traceback (most recent call last):
   ...
   MyWrapperException: ("I'm not in a good shape")

17
raise ... from ...thực sự là cách chính xác để làm điều này trong Python 3. Điều này cần nhiều upvote hơn.
Khỏa thân

NakedibleTôi nghĩ rằng đó là vì không may là hầu hết mọi người vẫn không sử dụng Python 3.
Tim Ludwinski

Điều này dường như xảy ra ngay cả khi sử dụng 'từ' trong python 3
Steve Vermeulen

Có thể được nhập vào Python 2. Hy vọng nó sẽ có một ngày.
Marcin Wojnarski

4
@ogrisel Bạn có thể sử dụng futuregói để đạt được điều này: python-future.org/compiverse_idioms.html#raising-exceptions Eg from future.utils import raise_raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda

19

Python 3 có mệnh đề raise...from ngoại lệ chuỗi. Câu trả lời của Glenn là tuyệt vời cho Python 2.7, nhưng nó chỉ sử dụng truy nguyên của ngoại lệ ban đầu và loại bỏ thông báo lỗi và các chi tiết khác. Dưới đây là một số ví dụ trong Python 2.7 bổ sung thông tin ngữ cảnh từ phạm vi hiện tại vào thông báo lỗi của ngoại lệ ban đầu, nhưng vẫn giữ nguyên các chi tiết khác.

Loại ngoại lệ được biết đến

try:
    sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
    self.user_id = sock_common.login(self.dbname, username, self.pwd)
except IOError:
    _, ex, traceback = sys.exc_info()
    message = "Connecting to '%s': %s." % (config['connection'],
                                           ex.strerror)
    raise IOError, (ex.errno, message), traceback

Hương vị của raisecâu lệnh lấy loại ngoại lệ làm biểu thức thứ nhất, hàm tạo của lớp ngoại lệ trong một tuple làm biểu thức thứ hai và truy nguyên là biểu thức thứ ba. Nếu bạn đang chạy sớm hơn Python 2.2, hãy xem các cảnh báo trên sys.exc_info().

Bất kỳ loại ngoại lệ

Đây là một ví dụ khác có mục đích chung hơn nếu bạn không biết loại ngoại lệ nào mà mã của bạn có thể phải nắm bắt. Nhược điểm là nó mất loại ngoại lệ và chỉ tăng RuntimeError. Bạn phải nhập tracebackmô-đun.

except Exception:
    extype, ex, tb = sys.exc_info()
    formatted = traceback.format_exception_only(extype, ex)[-1]
    message = "Importing row %d, %s" % (rownum, formatted)
    raise RuntimeError, message, tb

Sửa đổi tin nhắn

Đây là một tùy chọn khác nếu loại ngoại lệ sẽ cho phép bạn thêm ngữ cảnh vào nó. Bạn có thể sửa đổi thông báo của ngoại lệ và sau đó gửi lại.

import subprocess

try:
    final_args = ['lsx', '/home']
    s = subprocess.check_output(final_args)
except OSError as ex:
    ex.strerror += ' for command {}'.format(final_args)
    raise

Điều đó tạo ra dấu vết ngăn xếp sau đây:

Traceback (most recent call last):
  File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in <module>
    s = subprocess.check_output(final_args)
  File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory for command ['lsx', '/home']

Bạn có thể thấy rằng nó hiển thị dòng check_output()được gọi, nhưng thông báo ngoại lệ hiện bao gồm dòng lệnh.


1
Nơi ex.strerrorđến từ đâu? Tôi không thể tìm thấy bất kỳ cú đánh nào có liên quan cho điều đó trong các tài liệu Python. Có nên không str(ex)?
Henrik Heimbuerger

1
IOErrorđược bắt nguồn từ EnvironmentError, @hheimbuerger, cung cấp các thuộc tính errornostrerror.
Don Kirkby

Làm thế nào tôi có thể bọc một tùy ý Error, ví dụ ValueError, vào một RuntimeErrorbằng cách bắt Exception? Nếu tôi tái tạo câu trả lời của bạn cho trường hợp này, stacktrace sẽ bị mất.
Karl Richter

Tôi không chắc chắn những gì bạn đang hỏi, @karl. Bạn có thể gửi một mẫu trong một câu hỏi mới và sau đó liên kết với nó từ đây?
Don Kirkby

Tôi đã chỉnh sửa bản sao câu hỏi của OP tại stackoverflow.com/questions/23157766/ với một giải thích rõ ràng có tính đến câu trả lời của bạn trực tiếp. Chúng ta nên thảo luận ở đó :)
Karl Richter

12

Trong Python 3.x :

raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)

hoặc đơn giản

except Exception:
    raise MyException()

sẽ tuyên truyền MyExceptionnhưng in cả hai ngoại lệ nếu nó sẽ không được xử lý.

Trong Python 2.x :

raise Exception, 'Failed to process file ' + filePath, e

Bạn có thể ngăn in cả hai ngoại lệ bằng cách tắt __context__thuộc tính. Ở đây tôi viết một trình quản lý bối cảnh bằng cách sử dụng điều đó để nắm bắt và thay đổi ngoại lệ của bạn một cách nhanh chóng: (xem http://docs.python.org/3.1/l Library / stdtypes.html để biết cách họ làm việc)

try: # Wrap the whole program into the block that will kill __context__.

    class Catcher(Exception):
        '''This context manager reraises an exception under a different name.'''

        def __init__(self, name):
            super().__init__('Failed to process code in {!r}'.format(name))

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is not None:
                self.__traceback__ = exc_tb
                raise self

    ...


    with Catcher('class definition'):
        class a:
            def spam(self):
                # not really pass, but you get the idea
                pass

            lut = [1,
                   3,
                   17,
                   [12,34],
                   5,
                   _spam]


        assert a().lut[-1] == a.spam

    ...


except Catcher as e:
    e.__context__ = None
    raise

4
LoạiError: nâng cao: arg 3 phải là một dấu vết hoặc Không có
Glenn Maynard

Xin lỗi, tôi đã phạm sai lầm, bằng cách nào đó tôi nghĩ rằng nó cũng chấp nhận ngoại lệ và tự động nhận được thuộc tính truy nguyên của họ. Theo docs.python.org/3.1/reference/ , đây sẽ là e .__ tracBack__
ilya n.

1
@ilyan.: Python 2 không có e.__traceback__thuộc tính!
Jan Hudec

5

Tôi không nghĩ bạn có thể làm điều này trong Python 2.x, nhưng một cái gì đó tương tự như chức năng này là một phần của Python 3. Từ PEP 3134 :

Trong triển khai Python ngày nay, các ngoại lệ bao gồm ba phần: loại, giá trị và truy nguyên. Mô-đun 'sys', hiển thị ngoại lệ hiện tại trong ba biến song song, exc_type, exc_value và exc_tracBack, hàm sys.exc_info () trả về một tuple của ba phần này và câu lệnh 'nâng cao' có dạng ba đối số chấp nhận ba phần này. Thao tác ngoại lệ thường yêu cầu chuyển song song ba điều này, có thể tẻ nhạt và dễ bị lỗi. Ngoài ra, câu lệnh 'ngoại trừ' chỉ có thể cung cấp quyền truy cập vào giá trị, không phải truy nguyên. Thêm ' traceback ' thuộc tính với các giá trị ngoại lệ làm cho tất cả các thông tin ngoại lệ truy cập từ một nơi duy nhất.

So sánh với C #:

Các ngoại lệ trong C # chứa thuộc tính 'InternalException' chỉ đọc có thể trỏ đến một ngoại lệ khác. Tài liệu của nó [10] nói rằng "Khi một ngoại lệ X được ném là kết quả trực tiếp của ngoại lệ Y trước đó, thuộc tính InternalException của X sẽ chứa tham chiếu đến Y." Thuộc tính này không được VM đặt tự động; thay vào đó, tất cả các hàm tạo ngoại lệ đều lấy một đối số 'InternalException' tùy chọn để đặt nó một cách rõ ràng. Các ' nguyên nhân ' thuộc tính đáp ứng các mục đích tương tự như InnerException, nhưng PEP này đề xuất một hình thức mới của 'nâng' chứ không phải là mở rộng các nhà thầu của tất cả các trường hợp ngoại lệ. C # cũng cung cấp một phương thức GetBaseException nhảy trực tiếp đến cuối chuỗi InternalException;

Cũng lưu ý rằng Java, Ruby và Perl 5 cũng không hỗ trợ loại điều này. Trích dẫn lại:

Đối với các ngôn ngữ khác, cả Java và Ruby đều loại bỏ ngoại lệ ban đầu khi một ngoại lệ khác xảy ra trong mệnh đề 'bắt' / 'giải cứu' hoặc 'cuối cùng' / 'đảm bảo'. Perl 5 thiếu xử lý ngoại lệ có cấu trúc tích hợp. Đối với Perl 6, RFC số 88 [9] đề xuất một cơ chế ngoại lệ, mặc nhiên giữ lại các ngoại lệ được xâu chuỗi trong một mảng có tên @@.


Nhưng, tất nhiên, trong Perl5, bạn chỉ có thể nói "thú nhận qq {OH NOES! $ @}" Và không mất dấu vết ngăn xếp của ngoại lệ khác. Hoặc bạn có thể thực hiện loại của riêng bạn mà vẫn giữ ngoại lệ.
jrockway

4

Để tương thích tối đa giữa Python 2 và 3, bạn có thể sử dụng raise_fromtrong sixthư viện. https://six.readthedocs.io/#six.raise_from . Dưới đây là ví dụ của bạn (sửa đổi một chút cho rõ ràng):

import six

try:
  ProcessFile(filePath)
except Exception as e:
  six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)

3

Bạn có thể sử dụng lớp CausedException của tôi để xâu chuỗi các ngoại lệ trong Python 2.x (và thậm chí trong Python 3, nó có thể hữu ích trong trường hợp bạn muốn đưa ra nhiều hơn một ngoại lệ bị bắt vì một ngoại lệ mới được nêu ra). Có lẽ nó có thể giúp bạn.


2

Có lẽ bạn có thể lấy thông tin liên quan và đưa nó lên? Tôi đang nghĩ một cái gì đó như:

import traceback
import sys
import StringIO

class ApplicationError:
    def __init__(self, value, e):
        s = StringIO.StringIO()
        traceback.print_exc(file=s)
        self.value = (value, s.getvalue())

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

try:
    try:
        a = 1/0
    except Exception, e:
        raise ApplicationError("Failed to process file", e)
except Exception, e:
    print e

2

Giả định:

  • bạn cần một giải pháp, hoạt động cho Python 2 (đối với Python 3 thuần túy, hãy xem raise ... fromgiải pháp)
  • chỉ muốn làm phong phú thêm thông báo lỗi, ví dụ: cung cấp một số ngữ cảnh bổ sung
  • cần theo dõi ngăn xếp đầy đủ

bạn có thể sử dụng một giải pháp đơn giản từ các tài liệu https://docs.python.org/3/tutorial/errors.html#raising-exceptions :

try:
    raise NameError('HiThere')
except NameError:
    print 'An exception flew by!' # print or log, provide details about context
    raise # reraise the original exception, keeping full stack trace

Đầu ra:

An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

Có vẻ như phần quan trọng là từ khóa 'nâng cao' được đơn giản hóa. Điều đó sẽ nâng cao Ngoại lệ trong khối ngoại trừ.


Đây là giải pháp tương thích Python 2 & 3! Cảm ơn!
Andy Chase

Tôi nghĩ ý tưởng là đưa ra một loại ngoại lệ khác.
Tim Ludwinski

2
Đây không phải là một chuỗi các trường hợp ngoại lệ lồng nhau, chỉ tái hiện một ngoại lệ
Karl Richter

Đây là giải pháp python 2 tốt nhất, nếu bạn chỉ cần làm phong phú thông điệp ngoại lệ và có dấu vết ngăn xếp đầy đủ!
geekQ

Sự khác biệt giữa sử dụng tăng và tăng từ
biến
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.