Câu hỏi Tự nâng (ném) một ngoại lệ trong Python


Làm thế nào tôi có thể đưa ra một ngoại lệ trong Python để sau này nó có thể bị bắt thông qua một except khối?


1524
2018-01-12 21:07


gốc




Các câu trả lời:


Làm thế nào để tôi ném / nâng lên một ngoại lệ bằng Python?

Sử dụng hàm tạo Ngoại lệ cụ thể nhất phù hợp với ngữ nghĩa của bạn.

Cụ thể trong thông điệp của bạn, ví dụ:

raise ValueError('A very specific bad thing happened.')

Không tăng ngoại lệ chung

Tránh tăng ngoại lệ chung. Để nắm bắt nó, bạn sẽ phải nắm bắt tất cả các ngoại lệ cụ thể khác mà phân lớp nó.

Vấn đề 1: Ẩn lỗi

raise Exception('I know Python!') # Don't! If you catch, likely to hide bugs.

Ví dụ:

def demo_bad_catch():
    try:
        raise ValueError('Represents a hidden bug, do not catch this')
        raise Exception('This is the exception you expect to handle')
    except Exception as error:
        print('Caught this error: ' + repr(error))

>>> demo_bad_catch()
Caught this error: ValueError('Represents a hidden bug, do not catch this',)

Bài 2: Không bắt được

và các sản phẩm khai thác cụ thể hơn sẽ không bắt được ngoại lệ chung:

def demo_no_catch():
    try:
        raise Exception('general exceptions not caught by specific handling')
    except ValueError as e:
        print('we will not catch exception: Exception')


>>> demo_no_catch()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in demo_no_catch
Exception: general exceptions not caught by specific handling

Thực hành tốt nhất: raise tuyên bố

Thay vào đó, hãy sử dụng hàm tạo Ngoại lệ cụ thể nhất phù hợp với ngữ nghĩa của bạn.

raise ValueError('A very specific bad thing happened')

cũng cho phép một số đối số tùy ý được truyền cho hàm tạo:

raise ValueError('A very specific bad thing happened', 'foo', 'bar', 'baz') 

Các đối số này được truy cập bởi args thuộc tính trên đối tượng Ngoại lệ. Ví dụ:

try:
    some_code_that_may_raise_our_value_error()
except ValueError as err:
    print(err.args)

bản in

('message', 'foo', 'bar', 'baz')    

Trong Python 2.5, một thực tế message thuộc tính đã được thêm vào BaseException có lợi cho việc khuyến khích người dùng phân loại các ngoại lệ và ngừng sử dụng args, nhưng sự ra đời của message và khấu hao ban đầu của args đã được rút lại.

Thực hành tốt nhất: except mệnh đề

Khi bên trong một mệnh đề ngoại trừ, bạn có thể muốn, ví dụ, đăng nhập rằng một loại lỗi cụ thể đã xảy ra, và sau đó nâng cấp lại. Cách tốt nhất để làm điều này trong khi vẫn giữ lại dấu vết ngăn xếp là sử dụng câu lệnh nâng trần. Ví dụ:

logger = logging.getLogger(__name__)

try:
    do_something_in_app_that_breaks_easily()
except AppError as error:
    logger.error(error)
    raise                 # just this!
    # raise AppError      # Don't do this, you'll lose the stack trace!

Không sửa đổi lỗi của bạn ... nhưng nếu bạn nhấn mạnh.

Bạn có thể bảo vệ stacktrace (và giá trị lỗi) với sys.exc_info(), nhưng đây là cách dễ bị lỗi hơn và có vấn đề tương thích giữa Python 2 và 3, thích sử dụng trần raise để nâng cao.

Để giải thích - sys.exc_info() trả về kiểu, giá trị và truy nguyên.

type, value, traceback = sys.exc_info()

Đây là cú pháp trong Python 2 - lưu ý điều này không tương thích với Python 3:

    raise AppError, error, sys.exc_info()[2] # avoid this.
    # Equivalently, as error *is* the second object:
    raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]

Nếu muốn, bạn có thể sửa đổi điều xảy ra với khoản tăng mới của mình - ví dụ: thiết lập args mới cho cá thể:

def error():
    raise ValueError('oops!')

def catch_error_modify_message():
    try:
        error()
    except ValueError:
        error_type, error_instance, traceback = sys.exc_info()
        error_instance.args = (error_instance.args[0] + ' <modification>',)
        raise error_type, error_instance, traceback

Và chúng tôi đã bảo tồn toàn bộ truy nguyên trong khi sửa đổi các arg. Lưu ý rằng đây là không phải là phương pháp hay nhất và nó là cú pháp không hợp lệ trong Python 3 (làm cho việc giữ khả năng tương thích khó khăn hơn nhiều để làm việc).

>>> catch_error_modify_message()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in catch_error_modify_message
  File "<stdin>", line 2, in error
ValueError: oops! <modification>

Trong Python 3:

    raise error.with_traceback(sys.exc_info()[2])

Một lần nữa: tránh thao tác thủ công các tracebacks. nó là kém hiệu quả và dễ bị lỗi hơn. Và nếu bạn đang sử dụng luồng và sys.exc_info bạn thậm chí có thể nhận được traceback sai (đặc biệt là nếu bạn đang sử dụng xử lý ngoại lệ cho dòng điều khiển - mà cá nhân tôi có xu hướng tránh.)

Python 3, chuỗi ngoại lệ

Trong Python 3, bạn có thể chuỗi các ngoại lệ, trong đó bảo toàn các tracebacks:

    raise RuntimeError('specific message') from error

Hãy lưu ý:

  • điều này làm cho phép thay đổi loại lỗi được nâng lên và
  • đây là không phải tương thích với Python 2.

Phương thức không được chấp nhận:

Đây có thể dễ dàng ẩn và thậm chí có được vào mã sản xuất. Bạn muốn đưa ra một ngoại lệ, và thực hiện chúng sẽ làm tăng ngoại lệ, nhưng không phải là dự định!

Có giá trị trong Python 2, nhưng không có trong Python 3 như sau:

raise ValueError, 'message' # Don't do this, it's deprecated!

Chỉ có hợp lệ trong nhiều phiên bản cũ hơn của Python (2.4 và thấp hơn), bạn vẫn có thể thấy mọi người nâng cao chuỗi:

raise 'message' # really really wrong. don't do this.

Trong tất cả các phiên bản hiện đại, điều này thực sự sẽ làm tăng TypeError, bởi vì bạn không nâng loại BaseException. Nếu bạn không kiểm tra ngoại lệ đúng và không có người đánh giá nhận thức được vấn đề, nó có thể được đưa vào sản xuất.

Sử dụng ví dụ

Tôi tăng Ngoại lệ để cảnh báo người tiêu dùng về API của tôi nếu họ sử dụng nó không chính xác:

def api_func(foo):
    '''foo should be either 'baz' or 'bar'. returns something very useful.'''
    if foo not in _ALLOWED_ARGS:
        raise ValueError('{foo} wrong, use "baz" or "bar"'.format(foo=repr(foo)))

Tạo các loại lỗi của riêng bạn khi apropos

"Tôi muốn thực hiện một lỗi về mục đích, để nó sẽ đi vào ngoại trừ"

Bạn có thể tạo các loại lỗi của riêng mình, nếu bạn muốn chỉ ra điều gì đó cụ thể là sai với ứng dụng của bạn, chỉ cần phân lớp điểm thích hợp trong hệ thống phân cấp ngoại lệ:

class MyAppLookupError(LookupError):
    '''raise this when there's a lookup error for my app'''

và cách sử dụng:

if important_key not in resource_dict and not ok_to_be_missing:
    raise MyAppLookupError('resource is missing, and that is not ok.')

1898
2018-06-05 16:30



Cảm ơn vì điều này, đó là chính xác những gì tôi cần. Trần nhà raise là những gì tôi cần để có thể thực hiện gỡ lỗi lỗi tùy chỉnh ở nhiều cấp thực thi mã mà không vi phạm dấu vết ngăn xếp. - CaffeineConnoisseur
Đây là một câu trả lời tuyệt vời. Nhưng tôi vẫn làm việc với rất nhiều 2,7 mã và tôi thường thấy mình muốn thêm thông tin vào một ngoại lệ không mong muốn, như vị trí tệp đầu vào hoặc giá trị của một số biến, nhưng giữ nguyên ngăn xếp ban đầu và ngoại lệ. Tôi có thể ghi lại, nhưng đôi khi tôi không muốn nó đăng nhập, ví dụ: nếu mã cha mẹ cuối cùng xử lý nó. raise sys.exc_info()[0], (sys.exc_info()[1], my_extra_info), sys.exc_info()[2] dường như làm những gì tôi muốn, và tôi chưa bao giờ gặp vấn đề với nó. Nhưng nó cảm thấy hacky, và không phải là một thực tế được chấp nhận. Có cách nào tốt hơn? - Michael Scheper
Cảm ơn lời khen - nhưng thật đáng buồn, không có cách nào tốt hơn - không phải trên Python 2. - Aaron Hall♦
@brennanyoung Trong bối cảnh đó, tôi nghĩ rằng có thể gây nhầm lẫn khi nâng cao cú pháp SyntaxError - có lẽ bạn nên nâng cao một ngoại lệ tùy chỉnh. Tôi giải thích làm thế nào để ở đây: stackoverflow.com/a/26938914/541136 - Aaron Hall♦
Lưu ý rằng báo giá đầy đủ là "Tất cả các trường hợp ngoại lệ tích hợp, không có hệ thống được lấy từ lớp này. Tất cả các ngoại lệ do người dùng định nghĩa cũng phải được lấy từ lớp này". - Điều đó chủ yếu có nghĩa là bạn không nên sử dụng một trong 4 ngoại lệ không lấy được từ Exception như lớp cha mẹ của bạn - bạn có thể phân lớp một cái gì đó cụ thể hơn, và nên làm như vậy nếu nó có ý nghĩa. - Aaron Hall♦


KHÔNG LÀM NÀY. Nuôi trần Exception tuyệt đối không phải đó là việc làm đứng đắn; xem Câu trả lời tuyệt vời của Aaron Hall thay thế.

Không thể nhận được nhiều hơn nữa pythonic hơn này:

raise Exception("I know python!")

Xem tài liệu tuyên bố tăng cho python nếu bạn muốn biết thêm thông tin.


540
2018-01-12 21:08



Không, làm ơn! Điều này loại bỏ khả năng cụ thể về những gì bạn nắm bắt. Đó là HOÀN TOÀN sai cách để làm điều đó. Hãy xem câu trả lời tuyệt vời của Aaron Hall thay vì câu trả lời này. Đó là lần như thế này tôi muốn tôi có thể cung cấp cho nhiều hơn một downvote cho mỗi câu trả lời. - Dawood ibn Kareem
@ DavidWallace nó khủng khiếp mà điều này có rất nhiều upvotes: ( - Peter R
@ PeterR Nó là khủng khiếp như nhau mà nó có quá ít downvotes. BẤT CỨ BẤT KY NÀO đọc câu trả lời này, KHÔNG LÀM NÀY ĐÂY! Câu trả lời đúng là của Aaron Hall. - Dawood ibn Kareem
Tôi nghĩ rằng nên có một giải thích chi tiết hơn về lý do tại sao điều này là sai hoặc xấu như vậy. - Charlie Parker
@CharlieParker Có. Đó là phần đầu tiên của Câu trả lời của Aaron Hall. - Dinei


Đối với trường hợp phổ biến mà bạn cần phải ném một ngoại lệ để đáp ứng một số điều kiện bất ngờ và bạn không bao giờ có ý định bắt kịp, nhưng chỉ đơn giản là thất bại nhanh để cho phép bạn gỡ lỗi từ đó nếu nó xảy ra. AssertionError:

if 0 < distance <= RADIUS:
    #Do something.
elif RADIUS < distance:
    #Do something.
else:
    raise AssertionError("Unexpected value of 'distance'!", distance)

29
2018-05-19 04:55



Đây là một trường hợp tốt hơn cho ValueError hơn AssertionError bởi vì không có vấn đề với một xác nhận (bởi vì không có gì được thực hiện ở đây) - vấn đề là với một giá trị. Nếu bạn thực sự muốn AssertionError trong trường hợp này, hãy viết assert distance > 0, 'Distance must be positive'. Nhưng bạn không nên kiểm tra lỗi theo cách đó bởi vì các xác nhận có thể bị tắt (python -O). - Two-Bit Alchemist
@ Hai-BitAlchemist Điểm tốt. Ý tưởng đã bị mất trong đơn giản hóa, khi tôi viết ví dụ đơn giản ở trên. Trong nhiều trường hợp tương tự, đó là điều kiện không được liên kết với một giá trị cụ thể. Thay vào đó, ý nghĩa là "dòng chảy kiểm soát không bao giờ nên đến đây". - Evgeni Sergeev
@ Hai BitAlchemist Assertions có thể được tắt, có, nhưng sau đó bạn không nên sử dụng chúng để kiểm tra lỗi ở tất cả? - Evgeni Sergeev
Vâng nó phụ thuộc. Tôi sẽ không để cho đó là lỗi duy nhất của tôi kiểm tra trong một chương trình tôi dự định phân phối. Mặt khác, tôi có thể làm một chương trình chỉ dành cho đồng nghiệp của tôi và nói với họ rằng họ sử dụng nó với rủi ro của chính họ nếu họ chạy nó với -O. - Two-Bit Alchemist
@ Two-BitAlchemist Đối với tôi vai trò của các xác nhận không phải là kiểm tra lỗi trên mỗi lần (đó là thử nghiệm gì), nhưng họ thiết lập hàng rào trong mã mà một số lỗi không thể vượt qua được. Vì vậy, nó trở nên dễ dàng hơn để theo dõi và cô lập các lỗi, mà chắc chắn sẽ xảy ra. Đây chỉ là những thói quen tốt mà ít nỗ lực, trong khi thử nghiệm mất rất nhiều công sức và rất nhiều thời gian. - Evgeni Sergeev


Trong Python3 có 4 cú pháp khác nhau cho các ngoại lệ rasing:

1. raise exception 
2. raise exception (args) 
3. raise
4. raise exception (args) from original_exception

1. tăng ngoại lệ so với 2. tăng ngoại lệ (args)

Nếu bạn dùng raise exception (args)  để đưa ra một ngoại lệ thì args sẽ được in khi bạn in đối tượng ngoại lệ - như trong ví dụ bên dưới.

  #raise exception (args)
    try:
        raise ValueError("I have raised an Exception")
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error I have raised an Exception 



  #raise execption 
    try:
        raise ValueError
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error 

3.raise

raise tuyên bố mà không có bất kỳ đối số nào lại làm tăng ngoại lệ cuối cùng. Điều này rất hữu ích nếu bạn cần thực hiện một số hành động sau khi bắt ngoại lệ và sau đó muốn nâng cấp lại. Nhưng nếu trước đó không có ngoại lệ, raise tuyên bố tăng TypeError Ngoại lệ.

def somefunction():
    print("some cleaning")

a=10
b=0 
result=None

try:
    result=a/b
    print(result)

except Exception:            #Output ->
    somefunction()           #some cleaning
    raise                    #Traceback (most recent call last):
                             #File "python", line 8, in <module>
                             #ZeroDivisionError: division by zero

4. tăng ngoại lệ (args) từ original_exception

Câu lệnh này được sử dụng để tạo ra chuỗi ngoại lệ, trong đó một ngoại lệ được nêu ra để đáp ứng với một ngoại lệ khác có thể chứa các chi tiết của ngoại lệ ban đầu - như trong ví dụ dưới đây.

class MyCustomException(Exception):
pass

a=10
b=0 
reuslt=None
try:
    try:
        result=a/b

    except ZeroDivisionError as exp:
        print("ZeroDivisionError -- ",exp)
        raise MyCustomException("Zero Division ") from exp

except MyCustomException as exp:
        print("MyException",exp)
        print(exp.__cause__)

Đầu ra:

ZeroDivisionError --  division by zero
MyException Zero Division 
division by zero

23
2017-11-08 17:54





Đọc câu trả lời hiện có trước, đây chỉ là phụ lục.

Lưu ý rằng bạn có thể tăng ngoại lệ có hoặc không có đối số.

Thí dụ:

raise SystemExit

thoát khỏi chương trình nhưng bạn có thể muốn biết điều gì đã xảy ra. Bạn có thể sử dụng điều này.

raise SystemExit("program exited")

điều này sẽ in "chương trình đã thoát" thành stderr trước khi đóng chương trình.


5
2018-03-29 11:59