Python LanguageИсключения

Вступление

Ошибки, обнаруженные во время выполнения, называются исключениями и не являются безоговорочно фатальными. Большинство исключений не обрабатываются программами; можно писать программы, которые обрабатывают выбранные исключения. В Python есть специальные функции для обработки исключений и логики исключений. Кроме того, исключения имеют иерархию богатого типа, все наследуют от типа BaseException .

Синтаксис

  • исключение
  • raise # re-raise исключение, которое уже было поднято
  • повысить исключение из причины # Python 3 - установить исключение причины
  • исключить исключение из None # Python 3 - подавить весь контекст исключения
  • пытаться:
  • кроме [типов исключений] качестве идентификатора ] :
  • еще:
  • в конце концов:

Повышение исключений

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

def even_the_odds(odds):
    if odds % 2 != 1:
        raise ValueError("Did not get an odd number")
    
    return odds + 1

Устранение исключений

Используйте try...except: для исключения исключений. Вы должны указать как точное исключение, как можете:

try:
    x = 5 / 0
except ZeroDivisionError as e:
    # `e` is the exception object
    print("Got a divide by zero! The exception was:", e)
    # handle exceptional case
    x = 0  
finally:
    print "The END"
    # it runs no matter what execute.

Указанный класс исключений - в этом случае - ZeroDivisionError - ловит любое исключение из этого класса или любого подкласса этого исключения.

Например, ZeroDivisionError является подклассом ArithmeticError :

>>> ZeroDivisionError.__bases__
(<class 'ArithmeticError'>,)

И вот, все еще поймают ZeroDivisionError :

try:
    5 / 0
except ArithmeticError:
    print("Got arithmetic error")

Запуск кода очистки с окончательным

Иногда вы можете захотеть, чтобы что-то произошло независимо от того, что произошло, например, если вам нужно очистить некоторые ресурсы.

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

resource = allocate_some_expensive_resource()
try:
    do_stuff(resource)
except SomeException as e:
    log_error(e)
    raise  # re-raise the error
finally:
    free_expensive_resource(resource)

Этот шаблон часто лучше обрабатывается с помощью менеджеров контекста (используя оператор with ).

Переопределение исключений

Иногда вы хотите получить исключение только для его проверки, например, для ведения журнала. После проверки вы хотите, чтобы исключение продолжало распространяться, как и раньше.

В этом случае просто используйте оператор raise без параметров.

try:
    5 / 0
except ZeroDivisionError:
    print("Got an error")
    raise

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

try:
    5 / 0
except ZeroDivisionError as e:
    raise ZeroDivisionError("Got an error", e)

Но у этого есть недостаток в сокращении следа исключения до именно этого raise то время как raise без аргумента сохраняет исходный след исключения.

В Python 3 вы можете сохранить исходный стек с помощью синтаксиса raise from :

    raise ZeroDivisionError("Got an error") from e

Исключение цепочки с рейзом из

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

Python 3.x 3.0

Вы можете связать исключения, чтобы показать, как выполняется обработка исключений:

>>> try:
    5 / 0
except ZeroDivisionError as e:
    raise ValueError("Division failed") from e

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
ValueError: Division failed

Иерархия исключений

Обработка исключений происходит на основе иерархии исключений, определяемой структурой наследования классов исключений.

Например, IOError и OSError являются подклассами EnvironmentError . Код, который ловит IOError , не поймает OSError . Однако код, который ловит объект EnvironmentError будет захватывать как IOError s, так и OSError s.

Иерархия встроенных исключений:

Python 2.x 2.3
BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
       +-- ImportWarning
       +-- UnicodeWarning
       +-- BytesWarning
Python 3.x 3.0
BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

Исключения также являются объектами

Исключения - это просто обычные объекты Python, которые наследуются от встроенного BaseException . Сценарий Python может использовать оператор raise для прерывания выполнения, заставляя Python печатать трассировку стека стека вызовов в этой точке и представление экземпляра исключения. Например:

>>> def failing_function():
...     raise ValueError('Example error!')
>>> failing_function()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in failing_function
ValueError: Example error!

в котором говорится, что ValueError с сообщением 'Example error!' был поднят нашей failing_function() , которая была выполнена в интерпретаторе.

Код вызова может выбирать для обработки любых и всех типов исключений, которые может вызвать вызов:

>>> try:
...     failing_function()
... except ValueError:
...     print('Handled the error')
Handled the error

Вы можете получить объекты исключений, назначив их в части except... из кода обработки исключений:

>>> try:
...     failing_function()
... except ValueError as e:
...     print('Caught exception', repr(e))
Caught exception ValueError('Example error!',)

Полный список встроенных исключений Python вместе с их описаниями можно найти в документации на Python: https://docs.python.org/3.5/library/exceptions.html . И вот полный список, упорядоченный иерархически: Иерархия исключений .

Создание настраиваемых типов исключений

Создайте класс, наследующий от Exception :

class FooException(Exception):
    pass
try:
    raise FooException("insert description here")
except FooException:
    print("A FooException was raised.")

или другой тип исключения:

class NegativeError(ValueError):
      pass

def foo(x):
    # function that only accepts positive values of x
    if x < 0:
        raise NegativeError("Cannot process negative numbers")
    ...  # rest of function body
try:
    result = foo(int(input("Enter a positive integer: ")))  # raw_input in Python 2.x
except NegativeError:
    print("You entered a negative number!")
else:
    print("The result was " + str(result))

Не поймайте все!

Хотя часто возникает соблазн поймать каждое Exception :

try:
    very_difficult_function()
except Exception:
    # log / try to reconnect / exit gratiously
finally:
    print "The END"
    # it runs no matter what execute.

Или даже все (включая BaseException и все его дети, включая Exception ):

try:
    even_more_difficult_function()
except:
    pass  # do whatever needed

В большинстве случаев это плохая практика. Это может SystemExit больше, чем предполагалось, например SystemExit , KeyboardInterrupt и MemoryError - каждый из которых обычно должен обрабатываться иначе, чем обычные системные или логические ошибки. Это также означает, что нет четкого понимания того, что внутренний код может сделать неправильно и как правильно восстановиться из этого условия. Если вы поймаете каждую ошибку, вы не будете знать, какая ошибка произошла или как ее исправить.

Это чаще всего называют «маскировкой ошибок», и его следует избегать. Пусть ваша программа вылетает из строя вместо того, чтобы тихо провалиться или даже хуже, не справиться на более глубоком уровне исполнения. (Представьте, что это транзакционная система)

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

Захват нескольких исключений

Существует несколько способов уловить несколько исключений .

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

try:
    d = {}
    a = d[1]
    b = d.non_existing_field
except (KeyError, AttributeError) as e:
    print("A KeyError or an AttributeError exception has been caught.")

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

try:
    d = {}
    a = d[1]
    b = d.non_existing_field
except KeyError as e:
    print("A KeyError has occurred. Exception message:", e)
except AttributeError as e:
    print("An AttributeError has occurred. Exception message:", e)

Практические примеры обработки исключений

Вход пользователя

Представьте, что вы хотите, чтобы пользователь вводил число через input . Вы хотите, чтобы вход был числом. Вы можете использовать try / except :

Python 3.x 3.0
while True:
    try:
        nb = int(input('Enter a number: '))
        break
    except ValueError:
        print('This is not a number, try again.')

Примечание: Python 2.x вместо этого использует raw_input ; input функции существует в Python 2.x, но имеет другую семантику. В приведенном выше примере input также принимает выражения, такие как 2 + 2 которые вычисляют число.

Если вход не может быть преобразован в целое число, повышается значение ValueError . Вы можете поймать его except . Если никакое исключение не возбуждается, break выпрыгивает из цикла. После цикла nb содержит целое число.

Словари

Представьте, что вы повторяете список последовательных целых чисел, например range(n) , и у вас есть список словарей d , содержащий информацию о том, что нужно делать, когда вы сталкиваетесь с определенными целыми числами, скажем, пропустите d[i] следующие .

d = [{7: 3}, {25: 9}, {38: 5}]

for i in range(len(d)):
    do_stuff(i)
    try:
       dic = d[i]
       i += dic[i]
    except KeyError:
       i += 1

KeyError будет поднят, когда вы попытаетесь получить значение из словаря для ключа, которого не существует.

еще

Код в блоке else будет выполняться только в том случае, если код в блоке try восстановлен. Это полезно, если у вас есть код, который вы не хотите запускать, если генерируется исключение, но вы не хотите, чтобы исключения, брошенные этим кодом, были пойманы.

Например:

try:
    data = {1: 'one', 2: 'two'}
    print(data[1])
except KeyError as e:
    print('key not found')
else:
    raise ValueError()
# Output: one
# Output: ValueError

Обратите внимание, что этот вид else: нельзя комбинировать с if начиная с else-clause, в elif . Если у вас есть следующее , if это необходимо , чтобы остаться с отступом ниже, else: :

try:
    ...
except ...:
    ...
else:
    if ...:
        ...
    elif ...:
        ...
    else:
        ...