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


2265

Làm cách nào tôi có thể đưa ra một ngoại lệ trong Python để sau đó nó có thể bị bắt qua một exceptkhối?

Câu trả lời:


2938

Làm cách nào để tự ném / nâng một ngoại lệ trong 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 .

Hãy cụ thể trong tin nhắn của bạn, ví dụ:

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

Đừng nêu lên những ngoại lệ chung chung

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

Vấn đề 1: Ẩn bọ

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',)

Vấn đề 2: Không bắt

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 tiễn tốt nhất: raisetuyên bố

Thay vào đó, hãy sử dụng hàm tạo Exception 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 có thể cho phép một số lượng các đố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 argsthuộc tính trên Exceptionđối tượng. Ví dụ:

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

in

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

Trong Python 2.5, một messagethuộc tính thực tế đã được thêm vào để BaseExceptionkhuyến khích người dùng phân lớp Ngoại lệ và ngừng sử dụng args, nhưng việc giới thiệu messagevà khấu hao ban đầu của các đối số đã bị rút lại .

Thực tiễn tốt nhất: exceptmệnh đề

Ví dụ, khi bên trong một mệnh đề ngoại trừ, bạn có thể muốn ghi lại rằng một loại lỗi cụ thể đã xảy ra và sau đó nâng lại. Cách tốt nhất để làm điều này trong khi lưu giữ dấu vết ngăn xếp là sử dụng câu lệnh tă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!

Đừng sửa đổi lỗi của bạn ... nhưng nếu bạn khăng khăng.

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

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

type, value, traceback = sys.exc_info()

Đây là cú pháp trong Python 2 - lưu ý rằng đ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 bạn muốn, bạn có thể sửa đổi những gì xảy ra với mức tăng mới của mình - ví dụ: thiết lập mới argscho ví dụ:

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 toàn toàn bộ truy nguyên trong khi sửa đổi các đối số. Lưu ý rằng đây không phải là cách thực hành tốt nhất và đó là cú pháp không hợp lệ trong Python 3 (khiến việc giữ khả năng tương thích trở nên khó khăn hơn nhiều).

>>> 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 theo dõi. Nó ít hiệu quả hơn và dễ bị lỗi hơn. Và nếu bạn đang sử dụng luồng và sys.exc_infothậm chí bạn có thể nhận được dấu vết sai (đặc biệt là nếu bạn đang sử dụng xử lý ngoại lệ cho luồng điều khiển - điều 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ể xâu chuỗi các trường hợp ngoại lệ, bảo tồn các dấu vết:

    raise RuntimeError('specific message') from error

Hãy lưu ý:

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

Phương pháp không dùng nữa:

Chúng có thể dễ dàng ẩn và thậm chí 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ẽ đưa ra một ngoại lệ, nhưng không phải là một dự định!

Hợp lệ trong Python 2, nhưng không phải trong Python 3 là như sau:

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

Chỉ hợp lệ trong các phiên bản cũ hơn của Python (2.4 trở xuống), bạn vẫn có thể thấy mọi người nâng 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ẽ tăng một TypeError, bởi vì bạn không nâng một BaseExceptionloại. Nếu bạn không kiểm tra ngoại lệ phù hợp 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.

Cách sử dụng ví dụ

Tôi đưa ra Ngoại lệ để cảnh báo người tiêu dùng API của mình nếu họ sử dụng không đúng cách:

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 tạo ra một lỗi về mục đích, để nó đi vào ngoại lệ"

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 một cái 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.')

19
Cảm ơn vì điều này, đó chính xác là những gì tôi cần. Mức trần raiselà 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 phá vỡ 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 mã 2.7 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 và ngoại lệ. Tôi có thể đăng nhập nó, nhưng đôi khi tôi không muốn nó đăng nhập, ví dụ nếu cuối cùng mã cha mẹ 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

2
@brennanyoung Trong bối cảnh đó tôi nghĩ có thể khó hiểu khi nâng một SyntaxError - có lẽ bạn nên đưa ra 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

2
Lưu ý rằng trích dẫn đầy đủ là "Tất cả các ngoại lệ được tích hợp, không thoát khỏi hệ thống đều đượ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 nên đượ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 trường hợp ngoại lệ không xuất phát từ Exceptionlớ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ó hợp lý.
Aaron Hall

1
Trong ví dụ cho " Thực tiễn tốt nhất: ngoại trừ mệnh đề ", bạn sử dụng một AppErrorngoại lệ không xác định . Có thể tốt hơn để sử dụng một lỗi tích hợp nhưAttributeError
Stevoisiak

530

ĐỪNG LÀM ĐIỀU NÀY . Nâng trần Exceptionhoàn toàn không phải là điều nên làm; thay vào đó hãy xem câu trả lời xuất sắc của Aaron Hall .

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

raise Exception("I know python!")

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


67
Không, làm ơn Điều này loại bỏ tiềm năng để được cụ thể về những gì bạn bắt được. Đó là ENTIRELY 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à những lần như thế này, tôi ước tôi có thể đưa ra nhiều hơn một câu trả lời cho mỗi câu trả lời.
Dawood ibn Kareem

27
@PeterR Thật khủng khiếp không kém khi nó có quá ít lượt tải xuống. Để MỌI NGƯỜI đọc câu trả lời này, ĐỪNG LÀM ĐIỀU NÀY! Câu trả lời đúng là của Aaron Hall.
Dawood ibn Kareem

6
Tôi nghĩ nên có một lời giải thích chi tiết hơn về lý do tại sao điều này là sai hoặc quá tệ.
Charlie Parker

9
@CharlieParker Có. Đây là phần đầu tiên trong câu trả lời của Aaron Hall .
Dinei

5
Tại sao câu trả lời này không thể được gắn cờ để xóa? Nó đã có 93 lượt tải xuống rồi!
codeforester

54

Trong Python3 có 4 cú pháp khác nhau để loại trừ các ngoại lệ:

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 sử dụng raise exception (args) để đưa ra một ngoại lệ thì nó argssẽ được in khi bạn in đối tượng ngoại lệ - như trong ví dụ dưới đây.

  #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

raisetuyên bố mà không có bất kỳ đối số 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 lại nó. Nhưng nếu không có ngoại lệ trước đó, raisetuyên bố sẽ tăng TypeErrorNgoạ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. đưa ra ngoại lệ (args) từ original_exception

Tuyên bố này được sử dụng để tạo chuỗi ngoại lệ trong đó một ngoại lệ được nêu ra để đáp ứng với 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

7
Xin lưu ý rằng PEP8 thích exception(args)hơnexception (args)
Gloweye

Cũng có ý kiến raise exception(args) from Nonecho rằng ngoại lệ hiện đang hoạt động đã được xử lý và không còn được quan tâm. Nếu không, nếu bạn tăng một ngoại lệ bên trong một exceptkhối và nó không được xử lý, tracebacks cho cả trường hợp ngoại lệ sẽ được hiển thị phân cách bằng thông báo “Trong xử lý các ngoại lệ nêu trên, ngoại lệ khác xảy ra”
cg909

35

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

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

19
Đây là một trường hợp tốt ValueErrorhơn AssertionErrorvì không có vấn đề gì với một xác nhận (vì không có gì được thực hiện ở đây) - vấn đề là ở một giá trị. Nếu bạn thực sự muốn một AssertionErrortrong trường hợp nà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 đó vì các xác nhận có thể bị tắt ( python -O).
Nhà giả kim hai bit

1
@ 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à một điều kiện không liên quan đến một giá trị cụ thể. Thay vào đó, ý nghĩa là "dòng điều khiển không bao giờ nên đến đây".
Evgeni Sergeev

2
@ Các xác nhận hai-BitAlchemist có thể bị tắt, vâng, nhưng sau đó bạn không nên sử dụng chúng để kiểm tra lỗi?
Evgeni Sergeev

Vâng, nó phụ thuộc. Tôi sẽ không để đó là lỗi duy nhất của mình khi kiểm tra chương trình tôi dự định phân phối. Mặt khác, tôi có thể tạo một chương trình chỉ cho đồng nghiệp của mình và nói với họ rằng họ tự chịu rủi ro nếu họ điều hành nó -O.
Nhà giả kim hai bit

1
@ 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 mỗi lần (đó là thử nghiệm để làm gì), nhưng chúng thiết lập hàng rào trong mã mà một số lỗi nhất định không thể vượt qua. Vì vậy, nó trở nên dễ dàng hơn để theo dõi và cô lập các lỗi, chắc chắn sẽ xảy ra. Đây chỉ là những thói quen tốt mà tốn ít công sức, trong khi thử nghiệm tốn rất nhiều công sức và rất nhiều thời gian.
Evgeni Sergeev

12

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

Lưu ý rằng bạn có thể đưa ra các 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 chuyện gì đã xảy ra. Vì vậy, bạn có thể sử dụng chương trình này.

raise SystemExit("program exited")

điều này sẽ in "chương trình đã thoát" sang thiết bị lỗi chuẩn trước khi đóng chương trình.


2
Đây không phải là chống lại mô hình OOP sao? Tôi giả sử, trường hợp đầu tiên ném tham chiếu lớp và trường hợp thứ hai là một thể hiện của SystemExit. Sẽ không phải raise SystemExit()là sự lựa chọn tốt hơn? Tại sao cái đầu tiên thậm chí hoạt động?
cháy bỏng

2

Một cách khác để ném một ngoại lệ là assert. Bạn có thể sử dụng khẳng định để xác minh một điều kiện đang được thực hiện nếu không thì nó sẽ tăng AssertionError. Để biết thêm chi tiết có một cái nhìn ở đây .

def avg(marks):
    assert len(marks) != 0,"List is empty."
    return sum(marks)/len(marks)

mark2 = [55,88,78,90,79]
print("Average of mark2:",avg(mark2))

mark1 = []
print("Average of mark1:",avg(mark1))

2

Chỉ cần lưu ý: có những lúc bạn KHÔNG muốn xử lý các trường hợp ngoại lệ chung chung. Nếu bạn đang xử lý một loạt các tệp và ghi lại lỗi của mình, bạn có thể muốn bắt bất kỳ lỗi nào xảy ra đối với một tệp, ghi nhật ký và tiếp tục xử lý các tệp còn lại. Trong trường hợp đó, một

try:
    foo() 
except Exception as e:
    print(str(e)) # Print out handled error

chặn một cách tốt để làm điều đó Bạn vẫn sẽ muốn raisengoại lệ cụ thể để bạn biết ý nghĩa của chúng, mặc dù.


0

Bạn nên tìm hiểu tuyên bố nâng cao của python cho điều đó. Nó nên được giữ trong khối thử. Thí dụ -

try:
    raise TypeError            #remove TypeError by any other error if you want
except TypeError:
    print('TypeError raised')
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.