Вызов (выбрасывание) исключения в Python вручную

2267

Как я могу вызвать исключение в Python, чтобы потом его можно было перехватить через exceptблок?

TIMEX
источник

Ответы:

2939

Как вручную сгенерировать / вызвать исключение в 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.

Во всех современных версиях это на самом деле будет повышать 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
В примере « Best Practices: кроме предложения » вы используете неопределенное AppErrorисключение. Может быть лучше использовать встроенную ошибку, такую ​​какAttributeError
Stevoisiak
530

НЕ ДЕЛАЙТЕ ЭТОГО . Поднять голое Exceptionабсолютно не правильно; см . отличный ответ Аарона Холла .

Не может быть намного более питоническим, чем это:

raise Exception("I know python!")

Посмотрите документы поднятия для python, если вам нужна дополнительная информация.

Габриэль Херли
источник
67
Нет пожалуйста! Это исключает возможность быть конкретным в отношении того, что вы ловите. Это совершенно неверный способ сделать это. Взгляните на отличный ответ Аарона Холла вместо этого. Сейчас такие времена, я хотел бы дать более одного понижения голосов за ответ.
Дауд ибн Карим
27
@PeterR Столь же ужасно, что у него так мало голосов. НИКОМУ, ЧИТАЯ ЭТОТ ОТВЕТ, НЕ ДЕЛАЙТЕ ЭТОГО! Правильный ответ - Аарон Холл.
Дауд ибн Карим
6
Я думаю, что должно быть более подробное объяснение того, почему это неправильно или так плохо.
Чарли Паркер
9
@CharlieParker Есть. Это первая часть ответа Аарона Холла .
Dinei
5
Почему этот ответ нельзя пометить для удаления? Он уже получил 93 голосов!
codeforester
55

В Python3 есть 4 различных синтаксиса для выявления исключений:

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

1. поднять исключение против 2. поднять исключение (аргументы)

Если вы используете, 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заявление без каких-либо аргументов повторно вызывает последнее исключение. Это полезно, если вам необходимо выполнить некоторые действия после перехвата исключения, а затем повторно вызвать его. Но если раньше не было никаких исключений, 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. поднять исключение (аргументы) из оригинального исключения

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

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
N Randhawa
источник
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
@ Утверждения Two-BitAlchemist можно отключить, да, но тогда вам вообще не следует использовать их для проверки ошибок?
Евгений Сергеев
Смотря как. Я бы не допустил, чтобы это была моя единственная проверка ошибок в программе, которую я собирался распространять. С другой стороны, я мог бы сделать программу только для моих коллег и сказать им, что они используют ее на свой страх и риск, если они запускают ее с -O.
Двухразрядный алхимик
1
@ Two-BitAlchemist Для меня роль утверждений заключается не в проверке ошибок как таковой (для этого и нужно тестирование), а в том, что они устанавливают ограждения в коде, через которые определенные ошибки не могут пройти. Таким образом, становится легче отследить и изолировать ошибки, которые неизбежно произойдут. Это просто хорошие привычки, которые требуют мало усилий, в то время как тестирование требует много усилий и много времени.
Евгений Сергеев
12

Сначала прочтите существующие ответы, это всего лишь дополнение.

Обратите внимание, что вы можете вызывать исключения с аргументами или без них.

Пример:

raise SystemExit

Выход из программы, но вы можете знать, что произошло. Так что вы можете использовать это.

raise SystemExit("program exited")

это выведет "программу вышла" в stderr перед закрытием программы.

Анант Пракаш
источник
2
Разве это не против парадигмы ООП? Я предполагаю, что в первом случае создается ссылка на класс, а во втором - экземпляр SystemExit. Не raise SystemExit()будет ли лучший выбор? Почему первый вообще работает?
бурный
2

Еще один способ выбросить исключение assert. Вы можете использовать assert для проверки выполнения условия, если нет, то оно будет выполнено 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

Просто чтобы заметить: бывают случаи, когда вы действительно хотите обрабатывать общие исключения. Если вы обрабатываете кучу файлов и регистрируете свои ошибки, вы можете захотеть отследить любую ошибку, которая возникает для файла, зарегистрировать ее и продолжить обработку остальных файлов. В этом случае

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

заблокировать хороший способ сделать это. Вы все равно захотите использовать raiseконкретные исключения, чтобы знать, что они означают.

markemus
источник
0

Для этого вам следует изучить оператор повышения python. Он должен храниться внутри блока try. Пример -

try:
    raise TypeError            #remove TypeError by any other error if you want
except TypeError:
    print('TypeError raised')
Abhijeet.py
источник