Nâng cao ngoại lệ với một loại và thông báo khác, lưu giữ thông tin hiện có


139

Tôi đang viết một mô-đun và muốn có một hệ thống phân cấp ngoại lệ thống nhất cho các ngoại lệ mà nó có thể nêu ra (ví dụ: kế thừa từ một FooErrorlớp trừu tượng cho tất cả các foongoại lệ cụ thể của mô-đun). Điều này cho phép người dùng mô-đun nắm bắt những ngoại lệ cụ thể đó và xử lý chúng một cách rõ ràng, nếu cần. Nhưng nhiều trường hợp ngoại lệ được nêu ra từ mô-đun được nêu ra vì một số ngoại lệ khác; ví dụ: thất bại ở một số tác vụ vì OSError trên một tệp.

Những gì tôi cần là để bọc lại, ngoại lệ bắt được sao cho nó có một loại và thông điệp khác , để thông tin có sẵn hơn nữa trong hệ thống phân cấp lan truyền bởi bất cứ điều gì bắt được ngoại lệ. Nhưng tôi không muốn mất loại, tin nhắn và dấu vết ngăn xếp hiện có; đó là tất cả thông tin hữu ích cho ai đó đang cố gắng gỡ lỗi. Trình xử lý ngoại lệ cấp cao nhất là không tốt, vì tôi đang cố gắng trang trí ngoại lệ trước khi nó tiến xa hơn đến ngăn xếp lan truyền và trình xử lý cấp cao nhất đã quá muộn.

Điều này được giải quyết một phần bằng cách lấy foocác loại ngoại lệ cụ thể của mô-đun của tôi từ loại hiện có (ví dụ class FooPermissionError(OSError, FooError)), nhưng điều đó không làm cho việc bọc trường hợp ngoại lệ hiện tại trong một loại mới cũng như sửa đổi thông báo dễ dàng hơn.

Chuỗi PEP 3134 của Python và Tracebacks nhúng ngoại vi thảo luận về một thay đổi được chấp nhận trong Python 3.0 cho các đối tượng ngoại lệ của Chained, để chỉ ra rằng một ngoại lệ mới đã được đưa ra trong quá trình xử lý ngoại lệ hiện có.

Những gì tôi đang cố gắng làm có liên quan: Tôi cần nó cũng hoạt động trong các phiên bản Python trước đó và tôi cần nó không phải để xâu chuỗi, mà chỉ cho đa hình. Cách đúng đắn để làm điều này là gì?


Các ngoại lệ đã hoàn toàn đa hình - chúng đều là các lớp con của Ngoại lệ. Bạn đang cố làm gì vậy? "Thông điệp khác nhau" khá tầm thường với trình xử lý ngoại lệ cấp cao nhất. Tại sao bạn thay đổi lớp học?
S.Lott

Như đã giải thích trong câu hỏi (bây giờ, cảm ơn vì nhận xét của bạn): Tôi đang cố gắng trang trí một ngoại lệ mà tôi đã bắt gặp, để nó có thể tuyên truyền thêm với nhiều thông tin hơn nhưng không mất bất kỳ thông tin nào. Một xử lý cấp cao nhất là quá muộn.
bignose

Xin hãy xem lớp CausedException của tôi có thể làm những gì bạn muốn trong Python 2.x. Ngoài ra trong Python 3, nó có thể được sử dụng trong trường hợp bạn muốn đưa ra nhiều hơn một ngoại lệ ban đầu là nguyên nhân của ngoại lệ của bạn. Có lẽ nó phù hợp với nhu cầu của bạn.
Alfe


Đối với python-2 Tôi làm một cái gì đó tương tự như @DevinJeanpierre nhưng tôi chỉ thêm một thông báo chuỗi mới: except Exception as e-> raise type(e), type(e)(e.message + custom_message), sys.exc_info()[2]-> giải pháp này là từ một câu hỏi SO khác . Đây không phải là đẹp nhưng chức năng.
Trevor Boyd Smith

Câu trả lời:


197

Python 3 đã giới thiệu chuỗi ngoại lệ (như được mô tả trong PEP 3134 ). Điều này cho phép, khi đưa ra một ngoại lệ, trích dẫn một ngoại lệ hiện có là nguyên nhân gây ra lỗi:

try:
    frobnicate()
except KeyError as exc:
    raise ValueError("Bad grape") from exc

Do đó, ngoại lệ bị bắt trở thành một phần của (là nguyên nhân của hệ thống), ngoại lệ mới và có sẵn cho bất kỳ mã nào bắt được ngoại lệ mới.

Bằng cách sử dụng tính năng này, __cause__thuộc tính được đặt. Trình xử lý ngoại lệ tích hợp cũng biết cách báo cáo ngoại lệ của Nguyên nhân và bối cảnh của vụ ngoại lệ cùng với truy nguyên.


Trong Python 2 , có vẻ như trường hợp sử dụng này không có câu trả lời hay (như được mô tả bởi Ian BickingNed Batchelder ). Bummer.


4
Ian Bicking không mô tả giải pháp của tôi? Tôi rất tiếc rằng tôi đã đưa ra một câu trả lời đúng đắn như vậy, nhưng thật kỳ lạ khi điều này được chấp nhận.
Devin Jeanpierre

1
@bignose Bạn có quan điểm của tôi không chỉ từ việc đúng mà còn về việc sử dụng "frobnicate" :)
David M.

5
Hiện tại, chuỗi ngoại lệ thực sự là hành vi mặc định, thực tế nó là vấn đề ngược lại, loại bỏ ngoại lệ đầu tiên đòi hỏi phải làm việc, xem PEP 409 python.org/dev/peps/pep-0409
Chris_Rands

1
Làm thế nào bạn sẽ thực hiện điều này trong python 2?
selotape

1
Nó có vẻ hoạt động tốt (python 2.7)try: return 2 / 0 except ZeroDivisionError as e: raise ValueError(e)
alex

37

Bạn có thể sử dụng sys.exc_info () để lấy dấu vết và đưa ra ngoại lệ mới của bạn với dấu vết đã nói (như PEP đề cập). Nếu bạn muốn giữ nguyên kiểu và tin nhắn cũ, bạn có thể làm như vậy với ngoại lệ, nhưng điều đó chỉ hữu ích nếu bất cứ điều gì bắt được ngoại lệ của bạn tìm kiếm nó.

Ví dụ

import sys

def failure():
    try: 1/0
    except ZeroDivisionError, e:
        type, value, traceback = sys.exc_info()
        raise ValueError, ("You did something wrong!", type, value), traceback

Tất nhiên, điều này thực sự không hữu ích. Nếu có, chúng tôi sẽ không cần PEP đó. Tôi không khuyên bạn nên làm điều đó.


Devin, bạn lưu trữ một tham chiếu đến truy nguyên ở đó, bạn có nên xóa rõ ràng tham chiếu đó không?
Arafangion

2
Tôi đã không lưu trữ bất cứ thứ gì, tôi đã để lại dấu vết như một biến cục bộ có lẽ nằm ngoài phạm vi. Vâng, có thể hình dung là không, nhưng nếu bạn nêu ra những ngoại lệ như thế trong phạm vi toàn cầu thay vì trong các chức năng, bạn sẽ gặp vấn đề lớn hơn. Nếu khiếu nại của bạn chỉ là nó có thể được thực thi trong phạm vi toàn cầu, thì giải pháp thích hợp là không thêm phần soạn thảo không liên quan phải giải thích và không liên quan đến 99% sử dụng, mà là viết lại giải pháp sao cho không có điều đó là cần thiết trong khi làm cho nó có vẻ như không có gì khác biệt - như tôi đã làm bây giờ.
Devin Jeanpierre

4
Arafangion có thể đề cập đến một cảnh báo trong tài liệu Python chosys.exc_info() , @Devin. Nó nói, "Việc gán giá trị trả về theo dõi cho một biến cục bộ trong một hàm đang xử lý một ngoại lệ sẽ gây ra một tham chiếu vòng tròn." Tuy nhiên, một lưu ý sau đây nói rằng kể từ Python 2.2, chu trình có thể được dọn sạch, nhưng hiệu quả hơn là chỉ cần tránh nó.
Don Kirkby

5
Thêm chi tiết về cách khác nhau để ngoại lệ tái tăng bằng Python từ hai pythonistas giác ngộ: Ian BickingNed Batchelder
Rodrigue

11

Bạn có thể tạo loại ngoại lệ của riêng bạn mở rộng bất kỳ ngoại lệ nào bạn đã bắt gặp.

class NewException(CaughtException):
    def __init__(self, caught):
        self.caught = caught

try:
    ...
except CaughtException as e:
    ...
    raise NewException(e)

Nhưng hầu hết thời gian, tôi nghĩ sẽ đơn giản hơn để bắt ngoại lệ, xử lý nó và raisengoại lệ ban đầu (và giữ nguyên dấu vết) hoặc raise NewException(). Nếu tôi đang gọi mã của bạn và tôi đã nhận được một trong những trường hợp ngoại lệ tùy chỉnh của bạn, tôi hy vọng rằng mã của bạn đã xử lý bất kỳ ngoại lệ nào bạn phải nắm bắt. Vì vậy, tôi không cần phải truy cập nó.

Chỉnh sửa: Tôi tìm thấy phân tích này về các cách để ném ngoại lệ của riêng bạn và giữ ngoại lệ ban đầu. Không có giải pháp đẹp.


1
Trường hợp sử dụng mà tôi đã mô tả không phải để xử lý ngoại lệ; cụ thể là không xử lý nó, nhưng thêm một số thông tin bổ sung (một lớp bổ sung và tin nhắn mới) để có thể xử lý thêm trong ngăn xếp cuộc gọi.
bignose

2

Tôi cũng thấy rằng nhiều lần tôi cần một số "gói" cho các lỗi được nêu ra.

Điều này bao gồm cả trong một phạm vi chức năng và đôi khi chỉ bao gồm một số dòng bên trong một chức năng.

Tạo một trình bao bọc được sử dụng a decoratorcontext manager:


Thực hiện

import inspect
from contextlib import contextmanager, ContextDecorator
import functools    

class wrap_exceptions(ContextDecorator):
    def __init__(self, wrapper_exc, *wrapped_exc):
        self.wrapper_exc = wrapper_exc
        self.wrapped_exc = wrapped_exc

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not exc_type:
            return
        try:
            raise exc_val
        except self.wrapped_exc:
            raise self.wrapper_exc from exc_val

    def __gen_wrapper(self, f, *args, **kwargs):
        with self:
            for res in f(*args, **kwargs):
                yield res

    def __call__(self, f):
        @functools.wraps(f)
        def wrapper(*args, **kw):
            with self:
                if inspect.isgeneratorfunction(f):
                    return self.__gen_wrapper(f, *args, **kw)
                else:
                    return f(*args, **kw)
        return wrapper

Ví dụ sử dụng

người trang trí

@wrap_exceptions(MyError, IndexError)
def do():
   pass

Khi gọi dophương thức, đừng lo lắng IndexError, chỉ cầnMyError

try:
   do()
except MyError as my_err:
   pass # handle error 

quản lý bối cảnh

def do2():
   print('do2')
   with wrap_exceptions(MyError, IndexError):
       do()

bên trong do2, trong context manager, nếu IndexErrorđược nâng lên, nó sẽ được bọc và nâng lênMyError


1
Vui lòng giải thích "gói" sẽ làm gì với ngoại lệ ban đầu. Mục đích của mã của bạn là gì và nó kích hoạt hành vi gì?
alexis

@alexis - đã thêm một số ví dụ, hy vọng nó có ích
Aaron_ab

-2

Giải pháp tối ưu nhất cho nhu cầu của bạn nên là:

try:
     upload(file_id)
except Exception as upload_error:
     error_msg = "Your upload failed! File: " + file_id
     raise RuntimeError(error_msg, upload_error)

Bằng cách này, sau này bạn có thể in tin nhắn của mình và lỗi cụ thể được ném bởi chức năng tải lên


1
Điều đó bắt và sau đó ném đi đối tượng ngoại lệ, vì vậy không, nó không đáp ứng nhu cầu của câu hỏi. Câu hỏi hỏi làm thế nào để giữ ngoại lệ hiện có và cho phép cùng một ngoại lệ đó, với tất cả thông tin hữu ích mà nó chứa, để tiếp tục truyền bá ngăn xếp.
bignose
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.