Ручне підняття (кидання) винятку в Python


2262

Як я можу створити виняток у Python, щоб він пізніше був схоплений через exceptблок?

Відповіді:


2934

Як я вручну кидаю / піднімаю виняток у Python?

Використовуйте найбільш конкретний конструктор винятків, який семантично відповідає вашій проблемі .

Будьте конкретні у своєму повідомленні, наприклад:

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

Не створюйте загальних винятків

Уникайте підняття родового Exception. Щоб зловити його, вам доведеться спіймати всі інші більш конкретні винятки, які його підкласують.

Проблема 1: Приховування помилок

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

Наприклад:

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

Проблема 2: Не спіймаю

І більш конкретний улов не буде загальним винятком:

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

Кращі практики: raiseзаява

Натомість використовуйте найбільш конкретний конструктор винятків, який семантично відповідає вашій проблемі .

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

що також зручно дозволяє дозволити конструктору довільну кількість аргументів:

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

До цих аргументів звертається argsатрибут на Exceptionоб'єкті. Наприклад:

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

відбитки

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

У Python 2.5 messageбуло додано фактичний атрибут на BaseExceptionкористь заохочення користувачів до підкласу Винятки та припинення використання args, але введення messageта початкове припинення аргументів було відмінено .

Кращі практики: exceptпункт

Якщо всередині окрім пункту, окрім пункту, ви можете, наприклад, записати, що трапився певний тип помилки, а потім повторно підвищити. Найкращий спосіб зробити це при збереженні сліду стека - скористатися операцією підйому. Наприклад:

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!

Не змінюйте своїх помилок ... але якщо ви наполягаєте.

Ви можете зберегти стек-трек (і значення помилки) за допомогою sys.exc_info(), але це набагато більше схильний до помилок і має проблеми сумісності між Python 2 і 3 , вважайте за краще використовувати голий raiseдля повторного підйому.

Для пояснення - sys.exc_info()повертає тип, значення та прослідкування.

type, value, traceback = sys.exc_info()

Це синтаксис в Python 2 - зауважте, що це не сумісно з 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]

Якщо ви хочете, ви можете змінити те, що відбувається з вашим новим рейсом - наприклад, встановити новий argsдля примірника:

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

І ми зберегли цілу прослідку, змінюючи аргументи. Зауважте, що це не найкраща практика, і це недійсний синтаксис в Python 3 (що робить сумісність набагато складнішою для обходу).

>>> 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>

У Python 3 :

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

Знову ж таки: уникайте вручну маніпулювати слідами. Він менш ефективний і більше схильний до помилок. І якщо ви використовуєте нитку, і sys.exc_infoви навіть можете отримати неправильний прослідкування (особливо якщо ви використовуєте обробку виключень для потоку управління - чого я особисто схильний уникати.)

Python 3, Виключення ланцюга

У Python 3 ви можете ланцюжок винятків, які зберігають сліди:

    raise RuntimeError('specific message') from error

Бережись:

  • це дійсно дозволить змінити тип помилки встають, а
  • це не сумісно з Python 2.

Застарілі методи:

Вони легко ховаються і навіть потрапляють у виробничий код. Ви хочете підняти виняток, і виконуючи їх, викличете виняток, але не той, який призначений!

Дійсно в Python 2, але не в Python 3 є наступним:

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

Дійсний лише у значно старших версіях Python (2.4 і новіших), ви все ще можете бачити людей, що піднімають рядки:

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

У всіх сучасних версіях це фактично підвищить значення a TypeError, оскільки ви не піднімаєте BaseExceptionтип. Якщо ви не перевіряєте правильність винятку і не маєте рецензента, який обізнаний з проблемою, він може почати виробництво.

Приклад використання

Я піднімаю винятки, щоб попередити споживачів мого API, якщо вони його неправильно використовують:

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

Створюйте свої власні типи помилок під час призначення

"Я хочу зробити помилку навмисно, щоб вона перейшла в виняток"

Ви можете створити власні типи помилок, якщо ви хочете вказати, що з вашою програмою щось конкретно не так, просто підкласуйте відповідну точку в ієрархії винятків:

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

та використання:

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

19
Дякую за це, це саме те, що мені було потрібно. Це raiseте, що мені потрібно, щоб мати змогу виконувати власну налагодження помилок на кількох рівнях виконання коду, не порушуючи слід стека.
CaffeineConnoisseur

Це чудова відповідь. Але я все ще працюю з безліччю 2,7 коду, і часто мені здається, що хочеться додати інформацію до несподіваного винятку, як-от позиція вхідного файлу або значення деяких змінних, але зберігати початковий стек та виняток. Я можу ввійти в нього, але іноді не хочу, щоб він був записаний, наприклад, якщо в кінцевому рахунку батьківський код обробляє його. raise sys.exc_info()[0], (sys.exc_info()[1], my_extra_info), sys.exc_info()[2]здається, роблю те, що хочу, і я ніколи не стикався з проблемами. Але це відчуває хакі, і це не прийнята практика. Чи є кращий спосіб?
Майкл Шепер

2
@brennanyoung У цьому контексті я думаю, що може виникнути заплутаність у формуванні SyntaxError - напевно, ви повинні створити спеціальне виключення. Я пояснюю, як тут: stackoverflow.com/a/26938914/541136
Аарон Холл

2
Зауважте, що повна цитата - "Усі вбудовані винятки, що не входять в систему, походять з цього класу. Усі винятки, визначені користувачем, також повинні бути отримані з цього класу." - Це здебільшого означає, що ви не повинні використовувати одне з 4 винятків, які не походять з Exceptionбатьківського класу - ви можете підкласирувати щось більш конкретне, і слід робити це, якщо це має сенс.
Аарон Холл

1
У прикладі " Кращі практики: крім пункту " ви використовуєте невизначений AppErrorвиняток. Можливо, краще використовувати вбудовану помилку на кшталтAttributeError
Stevoisiak

530

НЕ робіть цього . Підняти оголене Exception- це зовсім не правильно; см відмінний відповідь Аарон Холла замість цього.

Не можна отримати набагато пітонічніше, ніж це:

raise Exception("I know python!")

Перегляньте документи операторів підвищення для python, якщо вам потрібна додаткова інформація.


67
Ні, будь ласка! Це знімає потенціал бути конкретним щодо того, що ви ловите. Це ВСІЧНО неправильний спосіб зробити це. Подивіться на відмінну відповідь Аарона Холла замість цієї. Такі часи я хотів би, щоб я міг дати більше одного зворотного відгуку за кожну відповідь.
Давуд ібн Карім

27
@PeterR Настільки ж жахливо, що у нього так мало зворотних даних. Щоб будь-хто читав цю відповідь, НЕ РОБИТИ ЦЕЙ ТАКОЖ! Правильна відповідь - це Аарон Холл.
Dawood ibn Kareem

6
Я думаю, має бути більш детальне пояснення, чому це неправильно чи так погано.
Чарлі Паркер

9
@CharlieParker Є. Це перша частина відповіді Аарона Холла .
Діней

5
Чому цю відповідь не можна позначити для видалення? У нього вже 93 знижки!
codeforester

54

У Python3 є 4 різні синтаксиси для ранжування винятків:

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

1. підвищити виняток проти 2. підвищити виняток (args)

Якщо ви використовуєте raise exception (args) для підняття винятку, тоді argsдрук буде надруковано під час друку об'єкта виключення - як показано в прикладі нижче.

  #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заява піднімає TypeErrorВиняток.

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. підняти виняток (args) з original_exception

Це твердження використовується для створення ланцюга винятків, в якому виняток, який піднімається у відповідь на інший виняток, може містити деталі вихідного винятку - як показано в прикладі нижче.

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__)

Вихід:

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

7
Будь ласка , зверніть увагу , що PEP8 воліє exception(args)більшexception (args)
Gloweye

Також raise exception(args) from Noneслід сказати, що наразі активний виняток оброблявся і вже не представляє інтересу. В іншому випадку, якщо ви підвищите виняток всередині exceptблоку, і він не обробляється, відстеження обох винятків буде показано розділеним повідомленням "Під час обробки вищевказаного винятку стався інший виняток"
cg909

35

Для загального випадку, коли вам потрібно кинути виняток у відповідь на деякі несподівані умови, і ви ніколи не збираєтеся ловити, а просто невдало швидко, щоб ви могли налагодити звідти, якщо це коли-небудь трапиться, - здається, найбільш логічним AssertionError:

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

19
Це кращий випадок, ValueErrorніж AssertionErrorтому, що немає жодних проблем із твердженням (оскільки жодне тут не робиться) - проблема полягає у значенні. Якщо ви дійсно хочете AssertionErrorв цьому випадку, пишіть assert distance > 0, 'Distance must be positive'. Але ви не повинні помилитися перевірити таким чином, оскільки твердження можна вимкнути ( python -O).
Двобітовий алхімік

1
@ Two-BitAlchemist Добре. Ідея була втрачена в спрощенні, коли я написав простий приклад вище. У багатьох подібних випадках це стан, який не пов'язаний з певним значенням. Скоріше, сенс - "контрольний потік сюди ніколи не повинен потрапляти".
Євгеній Сергєєв

2
@ Двобітні BitAlchemist твердження можна вимкнути, так, але тоді ви взагалі не повинні використовувати їх для перевірки помилок?
Євгеній Сергєєв

Ну це залежить. Я б не допустив, щоб це була моя єдина перевірка помилок у програмі, яку я мав намір поширити. З іншого боку, я міг би скласти програму лише для своїх колег і сказати їм, що вони використовують її на свій страх і ризик, якщо вони запускають її -O.
Двобітовий алхімік

1
@ Two-BitAlchemist Для мене роль тверджень не є перевірка помилок сама по собі (для чого це тестування), але вони встановлюють огорожі в коді, через який певні помилки не можуть пройти. Так стає простіше відстежувати та ізолювати помилок, що неминуче відбудеться. Це просто хороші звички, які забирають мало зусиль, а тестування займає багато зусиль і багато часу.
Євгеній Сергєєв,

12

Прочитайте спочатку наявні відповіді, це лише додаток.

Зауважте, що ви можете створювати винятки за допомогою аргументів або без них.

Приклад:

raise SystemExit

виходить із програми, але ви, можливо, захочете дізнатися, що сталося. Отже, ви можете використовувати це.

raise SystemExit("program exited")

це надрукує "програму, що вийшла" на stderr перед закриттям програми.


2
Чи не проти парадигми ООП? Я припускаю, що перший випадок закидає посилання на клас, а другий - екземпляр SystemExit. Не raise SystemExit()був би кращий вибір? Чому перший навіть працює?
Бурхливий

2

Ще один спосіб кинути винятки - це assert. Ви можете скористатись assrt, щоб перевірити, чи виконується умова, якщо ні, то вона підвищиться AssertionError. Докладніше дивіться тут .

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

Зазначимо: бувають випадки, коли ви хочете обробляти загальні винятки. Якщо ви обробляєте купу файлів і реєструєте свої помилки, можливо, ви захочете виявити будь-яку помилку, яка виникає для файлу, записати його та продовжити обробку решти файлів. У цьому випадку a

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

блокувати хороший спосіб це зробити. Ви все ще хочете raiseконкретних винятків, щоб знати, що вони означають.


0

Ви повинні навчитися заяві про підвищення python для цього. Його слід зберігати всередині пробного блоку. Приклад -

try:
    raise TypeError            #remove TypeError by any other error if you want
except TypeError:
    print('TypeError raised')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.