Рубрики
Без рубрики

Обработка исключений Python

Узнайте, как обрабатывать распространенные исключения Python и использовать их для улучшения вашего кода.

Автор оригинала: Sheena.

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

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

Первый вид ошибки, который мы рассмотрим, – это скромная синтаксическая ошибка. Синтаксические ошибки также известны как “ошибки синтаксического анализа”. В принципе, ошибки синтаксического анализа останавливают выполнение программы. Взгляните на это:

# parse_errors.py

print("program start")
print "program middle"
print("program end")

Теперь, если мы запустим этот скрипт с помощью Python2, он будет работать просто отлично:

$ python2 parse_errors.py

program start
program middle
program end

Но, конечно, Python3 дает нам синтаксическую ошибку:

$ python3 parse_errors.py

  File "parse_errors.py", line 2
    print "program middle"
                         ^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print("program middle")?

В этом случае ни один из вызовов print не выполняется. Python 3 не распознает parse_errors.py как допустимый код Python, поэтому он ничего не выполняет. И сообщение об ошибке довольно понятно.

Давайте рассмотрим другой пример:

# parse_errors2.py

while 1
    print("winning")

Обратите внимание на заметное отсутствие : . Это приведет к синтаксической ошибке как в Python 2, так и в Python 3:

$ python3 parse_errors2.py

  File "parse_errors2.py", line 1
    while 1
          ^
SyntaxError: invalid syntax

Еще одно интересное поведение-это то, что происходит, когда мы импортируем модули с синтаксическими ошибками:

# error_importer.py

print('importer start')
from parse_errors import *
print('importer end')

Теперь давайте запустим его с Python2:

$ python2 error_importer.py

importer start
program start
program middle
program end
importer end

Это все, как и ожидалось, потому что синтаксис полностью допустим в Python2. Но запуск его в Python 3 немного отличается:

$  python3 error_importer.py

importer start
Traceback (most recent call last):
  File "error_importer.py", line 2, in 
    from parse_errors import *
  File "/home/sheena/workspace/codementor/parse_errors.py", line 2
    print "program middle"
                         ^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print("program middle")?

Python 3 начинается с печати importer start , а затем завершается операцией импорта. На этот раз сообщение об ошибке немного более подробное. Он также имеет специальное имя: Traceback . Но прежде чем мы углубимся в Traceback s, важно немного знать о том, что называется “стеком вызовов”.

Взгляните на этот код (в нем нет никаких ошибок):

def func1():
    return 1

def func2():
    return func1()

def func3():
    return func2()

func3() ####

Как только различные функции определены, отмеченная строка помещает func3 в стек вызовов, func3 затем помещает func2 и func2 помещает func1 . Теперь func1 выскакивает из стека вызовов при возврате 1 в func2 , затем func2 выскакивает при возврате 1 в func3 .

Если все это pushing и popping звучит для вас запутанно, взгляните на это объяснение стека вызовов Python

Хорошо, теперь давайте представим ошибку:

# bubble.py

def func1():
    return 1/0   ##### ERROR

def func2():
    return func1()

def func3():
    return func2()

func3() ####

Если вы запустите этот код, он вызовет исключение :

$ python3 bubble.py

Traceback (most recent call last):
  File "bubble.py", line 10, in 
    func3() ####
  File "bubble.py", line 8, in func3
    return func2()
  File "bubble.py", line 5, in func2
    return func1()
  File "bubble.py", line 2, in func1
    return 1/0   ##### ERROR
ZeroDivisionError: division by zero

Как это работает?

Ну, все начинается так же, как и раньше, с того, что func3 , func2 и func1 помещаются в стек вызовов. Затем возникает ошибка времени выполнения в func1 , когда он пытается разделить на ноль. Это * вызывает Исключение *. Исключение - это особый вид объекта Python, который хранит информацию о том, что пошло не так. Теперь, когда Исключение *пузырится* через стек вызовов. Traceback` – это сообщение, описывающее стек вызовов в том виде, в каком он вызвал ошибку, каждый кадр стека описан кратко.

В некотором смысле, Исключение можно рассматривать как особый вид возврата . Взгляните на эти две функции:

def func_a():
    return  "no error"
    print("this line never executes")

def func_b():
    a = 1/0
    print("this line never executes")

Вызов func_a даст вам хорошую строку. Вызов func_b вызовет Исключение и остановит выполнение. И ни один из этих операторов печати никогда не будет выполнен.

Исправление ошибок Python

Чтение Traceback довольно просто (хотя саму ошибку не всегда очень легко исправить). Вот наш Traceback из прошлого:

Traceback (most recent call last):
  File "bubble.py", line 10, in 
    func3() ####
  File "bubble.py", line 8, in func3
    return func2()
  File "bubble.py", line 5, in func2
    return func1()
  File "bubble.py", line 2, in func1
    return 1/0   ##### ERROR
ZeroDivisionError: division by zero

Последняя часть Traceback описывает фактическое Исключение , которое было вызвано. Двигаясь немного вверх, вы видите, что Исключение было вызвано func1 в строке 2 нашего скрипта. Перейдя еще немного вверх, вы можете увидеть, что func2 был вызван func1 в строке 5 скрипта и так далее.

Пример, который я привел выше, был довольно простым – ошибка была жестко закодирована в сценарии, и не было никаких аргументов, отправленных различным функциям. Исключение обычно возникает из-за того, что что-то неожиданное пошло не так. В большинстве случаев полезный код намного сложнее, и иногда Traceback недостаточно, чтобы выявить ошибку самостоятельно. В этом случае у вас есть несколько различных инструментов, которые могут помочь. Я не буду подробно объяснять эти инструменты, так как это немного выходит за рамки этой статьи, но поскольку мы говорим об исправлении ошибок…

  • print : Вы можете распечатать значения переменных и тому подобное, чтобы получить представление о том, что произошло. Это обычно называется “отладкой printf”. Это быстро и грязно, и обычно есть лучшие способы сделать что-то. Но если вы обнаружите, что выполняете отладку печати, пожалуйста, не забудьте удалить инструкции печати, как только вы закончите.
  • logging.debug : В Python есть специальный модуль logging , который намного более интеллектуальный, чем print , и обычно считается лучшей практикой, но есть также кривая обучения. Если вы еще не знаете, как использовать модуль ведения журнала, не волнуйтесь слишком сильно, так как это не обязательно (но это довольно приятно). Вы можете узнать об этом здесь
  • Python также имеет встроенный отладчик, который можно использовать для пошагового просмотра кода. Вы можете найти последнюю документацию здесь . Он позволяет вам исследовать и взаимодействовать с вашей программой во время ее работы. Это может быть очень полезно.
  • Есть также несколько библиотек, которые делают Traceback более информативным. Одним из примеров является Вакцина против туберкулеза .

Конечно, многое можно сказать о предотвращении ошибок в первую очередь. Но опять же, это немного выходит за рамки. Здесь стоит рассмотреть следующие вещи:

  • Тестирование вашего кода. Я предлагаю использовать pytest и иногда doctest
  • Типа намекает. Использование подсказок типа в коде может полностью устранить целые категории ошибок, будучи полезным в качестве документации. Взгляните на my py для получения более подробной информации.

Все в Python-это объект. Это включает в себя Исключения . Когда возникает Exception , это означает, что создается экземпляр класса Exception . И поскольку класс Exception на самом деле является классом, его можно разделить на подклассы.

Здесь мы видим, что KeyError является подклассом Исключения :

>>> Exception

>>> Exception.__doc__
'Common base class for all non-exit exceptions.'
>>> KeyError

>>> KeyError.__doc__
'Mapping key not found.'
>>> issubclass(KeyError,Exception)
True

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

Мы можем сделать то же самое с IndexError

>>> IndexError

>>> IndexError.__doc__
'Sequence index out of range.'
>>> issubclass(IndexError,Exception)
True

Взгляните на это:

some_function()
another_function()

Если some_function вызывает Исключение , то another_function никогда не будет вызвана при запуске этого скрипта. Программа просто рухнет. Это может быть…раздражающим. Ошибки-это часть жизни. Когда вы осознаете допущенную вами ошибку, вы справляетесь с ней, а затем переходите к тому, что будет дальше. Python может быть таким же ответственным.

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

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

Python Попробуйте, Кроме

Я буду придерживаться синтаксиса Python3. Он немного отличается от Python2, но в основном работает так же.

Рассмотрим следующую программу:

# divider.py

print("welcome to the number divider program")
x = float(input("please input a number:\n"))
y = float(input("please input another number:\n"))
answer = x/y
print(f"The answer is {answer}")

Давайте немного поиграем с ним. Во-первых, давайте введем несколько целых чисел:

$ python3 divider.py

welcome to the number divider program
please input a number:
1
please input another number:
2
The answer is 0.5

Теперь о некоторых поплавках:

$ python3 divider.py

welcome to the number divider program
please input a number:
12.3
please input another number:
45.6
The answer is 0.26973684210526316

Давайте разберем его, введя текст на английском языке:

$ python3 divider.py

welcome to the number divider program
please input a number:
a number
Traceback (most recent call last):
  File "divider.py", line 11, in 
    x = float(input("please input a number:\n"))
ValueError: could not convert string to float: 'a number'

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

Теперь давайте разделим на ноль:

$ python3 divider.py

welcome to the number divider program
please input a number:
1
please input another number:
0
Traceback (most recent call last):
  File "divider.py", line 13, in 
    answer = x/y
ZeroDivisionError: float division by zero

И последнее, но не менее важное: запустите программу и нажмите Ctrl+C частично:

$ python3 divider.py
welcome to the number divider program
please input a number:
4
please input another number:
^CTraceback (most recent call last):
  File "divider.py", line 12, in 
    y = float(input("please input another number:\n"))
KeyboardInterrupt

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

Исключение улова Python

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

# divider2.py

def get_user_input(message:str) -> float:
     while True:
         try:
             return float(input(f"{message}:\n"))
         except:
             print("Oops!  That was no valid number.  Try again...")


print("welcome to the number divider program")
x = get_user_input("please input a number")
y = get_user_input("please input another number")
answer = x/y
print(f"The answer is {answer}")

Давайте подробнее рассмотрим функцию get_user_input .

Как мы знаем из последней версии кода, float(input(f"{message}:\n")) может вызвать Исключение . Мы хотели бы иметь возможность оправиться от этого Исключения . Поэтому мы поместим его в блок try . Затем блок except говорит, что делать, если произойдет Исключение .

Теперь давайте попробуем запустить программу и ввести некоторые недопустимые номера:

python3 divider2.py

welcome to the number divider program
please input a number:
12,3
Oops!  That was no valid number.  Try again...
please input a number:
12.3
please input another number:
foooooooooooo
Oops!  That was no valid number.  Try again...
please input another number:
baaaaaaaa
Oops!  That was no valid number.  Try again...
please input another number:
2
The answer is 6.15

Замечательно! Теперь мы можем иметь дело с недопустимыми входными данными. Но в этом коде есть серьезная проблема, можете ли вы ее обнаружить?

Улавливание правильного исключения

Давайте попробуем выйти из программы до ее завершения ( Ctrl+C ):

welcome to the number divider program
please input a number:
^COops!  That was no valid number.  Try again...
please input a number:
1
please input another number:
^COops!  That was no valid number.  Try again...
please input another number:
2
The answer is 0.5

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

Мы изменим нашу функцию, чтобы она выглядела так:

def get_user_input(message:str) -> float:
     while True:
         try:
             return float(input(f"{message}:\n"))
         except ValueError:
             print("Oops!  That was no valid number.  Try again...")


print("welcome to the number divider program")
x = get_user_input("please input a number")
y = get_user_input("please input another number")
answer = x/y
print(f"The answer is {answer}")

А затем мы снова запускаем сценарий. На этот раз мы введем неверный номер, прежде чем попытаемся выйти.

welcome to the number divider program
please input a number:
1..2
Oops!  That was no valid number.  Try again...
please input a number:
^CTraceback (most recent call last):
  File "divider.py", line 10, in 
    x = get_user_input("please input a number")
  File "divider.py", line 4, in get_user_input
    return float(input(f"{message}:\n"))
KeyboardInterrupt

Хорошо выглядишь! Теперь мы можем восстановиться после некоторых ошибок пользователя! Но…вывод программы довольно уродлив, когда пользователь пытается выйти, так что давайте сделаем это красиво:

def get_user_input(message:str) -> float:
     while True:
         try:
             return float(input(f"{message}:\n"))
         except ValueError:
             print("Oops!  That was no valid number.  Try again...")


try:
    print("welcome to the number divider program")
    x = get_user_input("please input a number")
    y = get_user_input("please input another number")
    answer = x/y
    print(f"The answer is {answer}")
except KeyboardInterrupt:
    print("We bid you farewell!")
    print("Please rate us on the app store")

Теперь , если пользователь нажмет Ctrl+C , он получит приятное сообщение. Обратите внимание на тот факт, что блок try имеет длину в несколько строк. Это означает, что любое прерывание клавиатуры, выданное в любой момент во время выполнения любой из этих строк, будет поймано и обработано блоком , кроме .

Работа с объектами исключений

Далее, давайте сделаем наше сообщение об ошибке ValueError немного более информативным:

def get_user_input(message:str) -> float:
     while True:
         try:
            return float(input(f"{message}:\n"))
         except ValueError as e:   ###
            print(f"Oops! {e}. Try again...")

Если вы используете как , как указано выше, у вас будет доступ к объекту Exception . В этом случае мы просто покажем пользователю фактическое сообщение об ошибке. Обратите внимание, что это не полный Трассировка , и что e является ValueError объектом, а не строкой ! Он просто имеет очень дружественное представление string .

Запустите программу еще раз и введите в нее неверные данные:

welcome to the number divider program
please input a number:
qw
Oops! could not convert string to float: 'qw'. Try again...

Несколько блоков, кроме блоков, и оператор raise

Давайте добавим некоторые ненужные осложнения в наш код:

def get_user_input(message:str) -> float:
     while True:
        try:
            return float(input(f"{message}:\n"))
        except KeyboardInterrupt:
            print("Interrupted the process when an input was required...")
            raise
        except ValueError:
            print(f"Oops!. Try again...")

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

Затем есть оператор raise , который повторно вызывает Исключение , которое было поймано. Более подробную версию этого кода можно увидеть ниже:

def get_user_input(message:str) -> float:
     while True:
        try:
            return float(input(f"{message}:\n"))
        except KeyboardInterrupt as e:
            print("Interrupted the process when an input was required...")
            raise e
        except ValueError:
            print(f"Oops!. Try again...")

На самом деле, вы можете вызвать Исключение s, когда захотите, с помощью raise . Например:

>>> raise Exception("Oh Noes!")
Traceback (most recent call last):
  File "", line 1, in 
Exception: Oh Noes!

Я оставлю это на ваше усмотрение, уважаемый читатель, чтобы ввести некоторую обработку ошибок для ZeroDivisionError .

Что, кроме блока?

Рассмотрим следующее:

class Error1(Exception): pass

class Error2(Error1): pass

class Error3(Error2): pass

try:
    raise Error2()  ###
except Error1:
    print("1")
except Error2:
    print("2")
except Error3:
    print("3")

print('done')

Теперь Ошибка 2 является подклассом Ошибка 1 . Это означает, что наш первый кроме блок будет выполнен. После его завершения ни один из других блоков , кроме , не будет рассматриваться. Другими словами, Python выполняет только первый соответствующий блок , за исключением , и знает о наследовании.

Если вы запустите приведенный выше код, вы получите:

1
done

Если бы вместо этого вы подняли Error1 или Error2 , результат был бы точно таким же. Попробуйте сами и посмотрите, что произойдет, если вы переставите блоки “кроме”.

Например, что произойдет, если вы запустите этот сценарий?

class Error1(Exception): pass

class Error2(Error1): pass

class Error3(Error2): pass

try:
    raise Error2()  ###
except Error2:
    print("2")
except Error1:
    print("1")
except Error3:
    print("3")

print('done')

Одно исключение, несколько типов исключений

Вы также можете иметь один оператор except с несколькими типами ошибок, например:

try:
    foo()
except (KeyError, ZeroDivisionError , NameError):
    handle_error()

Вы получите объект ошибки, как и раньше:

try:
    foo()
except (KeyError, ZeroDivisionError , NameError) as e:
    handle_error(e)

Таким образом , если foo() вызывает KeyError , то e будет экземпляром Key Error , а если foo() вызывает NameError , то e может быть экземпляром NameError .

Ещё

Вы все знаете о если...еще верно? Ну, else также может использоваться в контексте Обработки исключений . Рассмотрим следующее:

def useless_function(key):
    d = {1:1}
    try:
        print(d[key])
    except KeyError:
        print("KeyError handled")
    else:
        print("else")

Теперь давайте вызовем бесполезную функцию:

>>> useless_function(1)
1
else
>>> useless_function(2)
KeyError handled

Поэтому, если есть ключевая ошибка, она обрабатывается блоком except . В противном случае выполняется блок else .

Вот бесполезная функция в виде псевдокода:

def useless_function(key):
    d = {1:1}
    try:
        print(d[key])
    if there is a KeyError:
        print("KeyError handled")
    else:
        print("else")

Давайте добавим еще один кроме блока:

def useless_function(key):
    d = {1:1}
    try:
        print(d[key])
    except KeyError:
        print("KeyError handled")
    except NameError:
        handle_name_error()
    else:
        print("else")

Теперь псевдокод выглядит следующим образом:

def useless_function(key):
    d = {1:1}
    try:
        print(d[key])
    if there is a KeyError:
        print("KeyError handled")
    elif there is a NameError:
        handle_name_error()
    else:
        print("else")

Окончательно

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

try:
    raise KeyError()
except KeyError:
    print("KeyError")
else:
    print("No error")
finally:
    print("cleanup finally")


print("afterwards")

Если вы запустите приведенный выше код, вы получите этот вывод:

KeyError
cleanup finally
afterwards

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

try:
    pass ######
except KeyError:
    print("KeyError")
else:
    print("No error")
finally:
    print("cleanup finally")


print("afterwards")

Выход сейчас:

No error
cleanup finally
afterwards

Давайте вернем нашу ошибку и поднимем ее в блоке except

try:
    raise KeyError()    ######
except KeyError:
    print("KeyError")
    raise               ######
else:
    print("No error")
finally:
    print("cleanup finally")


print("afterwards")

Теперь это происходит:

KeyError
cleanup finally
Traceback (most recent call last):
  File "animal_errors.py", line 4, in 
    raise KeyError()
KeyError

Давайте вместо этого поднимем NameError :

try:
    raise NameError()
except KeyError:
    print("KeyError")
    raise
else:
    print("No error")
finally:
    print("cleanup finally")


print("afterwards")

Вывод теперь выглядит следующим образом:

cleanup finally
Traceback (most recent call last):
  File "animal_errors.py", line 4, in 
    raise NameError()
NameError

В этом шаблоне всегда печатается “очистка, наконец”. Он присутствует в каждом отдельном выводе выше. Это может быть очень полезно, особенно если вам нужно выполнить какое-то действие очистки, НЕСМОТРЯ НИ НА ЧТО, так как вы можете поместить это действие в блок finally .

Обратите внимание, что когда Исключение повторно вызывается или не обрабатывается по какой-либо причине, блок finally выполняется до того, как Исключению будет разрешено перемещаться по стеку вызовов.

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

try:
    raise NameError()
finally:
    print("cleanup finally")


print("afterwards")

Выход:

cleanup finally
Traceback (most recent call last):
  File "animal_errors.py", line 4, in 
    raise NameError()
NameError

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

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

Будьте конкретны в отношении исключения, которое вы ловите!!!!!

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

def get_user_input(message:str) -> float:
    while True:try:
        return float(inpit(f"{message}:\n"))
    except:
        print(f"Oops! Try again...")

Это должно выглядеть несколько знакомо. У нас есть блок “кроме всех”, который мешает нашему прерыванию клавиатуры. Мы уже видели, что это может раздражать. Но это может быть гораздо более зловещим, чем это.

Я добавил дополнительную ошибку в код выше, вы можете это увидеть? Я использовал слово input вместо input . Теперь запустите код…если осмелитесь.

Вот как выглядит результат:

Oops! Try again...
Oops! Try again...
Oops! Try again...
Oops! Try again...
Oops! Try again...
Oops! Try again...
Oops! Try again..
etc

И когда вы пытаетесь убежать с помощью прерывания клавиатуры, он просто говорит Ой! Попробуйте еще раз... . Это ужасно.

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

Когда ваш код терпит неудачу, а это произойдет, он должен потерпеть неудачу громко и информативно. Таким образом, вы можете вмешаться и быстро исправить ошибку. Замалчивание и искажение ошибок-плохая идея.

Представьте, что вы пишете какой-то код для управления атомной электростанцией. Представьте, что вы искажаете ошибки в своем коде. На ум приходит одно слово: БУМ!

Если вы хотите узнать больше об этом конкретном анти-паттерне, взгляните на этот занимательный пост в блоге .

Будьте конкретны в отношении того, где могут возникать исключения

Считать…

try:
    this()
    that()
    other_stuff()
    this_other_thing()
    a_complicated function() ####
    some_heavy_stuff()
    etc()
except SomeException:
    handle_exception()

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

  • Вышеприведенный факт просто не ясен из этого кода. Вам придется серьезно покопаться, если вам нужно это выяснить.
  • Если бы какая-то другая строка , которая не должна была вызывать То же самое исключение сделала вызывала | Какое-то исключение , то вы бы не знали, что возникла проблема, потому что предложение except просто обработало бы ее.

Это был бы лучший способ написать свой код:

this()
that()
other_stuff()
this_other_thing()

try:
    a_complicated function() ####
except SomeException:
    handle_exception()
else:
    some_heavy_stuff()
    etc()

Эта версия кода понятна и не скрывает никаких ошибок.

Расположите свои блоки в удобном порядке

Вот некоторые коды, которые мы рассмотрели ранее:

class Error1(Exception): pass

class Error2(Error1): pass

class Error3(Error2): pass

try:
    raise Error2()  ###
except Error1:
    print("1")
except Error2:
    print("2")
except Error3:
    print("3")

print('done')

Запуск этого дает вам:

1
done

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

Другими словами, этот код делает то же самое, независимо от того, какая из трех ошибок вызвана :

class Error1(Exception): pass

class Error2(Error1): pass

class Error3(Error2): pass

try:
    raise Error2()  # or Error1() or Error3
except Error1:
    print("1")

print('done')

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

class Error1(Exception): pass

class Error2(Error1): pass

class Error3(Error2): pass

try:
    raise Error2()  ###
except Error3:
    print("3")
except Error2:
    print("2")
except Error1:
    print("1")

print('done')

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

В общем

Этот раздел можно резюмировать следующим образом:

  • Обрабатывайте только те Исключения , которые вы явно хотите обработать.
  • Ошибки в вашем коде должны выглядеть как ошибки.
  • Лучше всего узнать об ошибках как можно раньше.
  • Если произошла ошибка, вы хотите иметь достаточно информации, чтобы исправить ее.

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

Честно говоря, о исключении можно сказать гораздо больше . В этой статье речь шла об обработке Исключений , но они также могут быть использованы по-разному. Если вы действительно хотите быть профессионалом, то следующее, что я бы посоветовал вам изучить, – это как эффективно использовать AssertionError s//. Вы также можете узнать о создании и создании пользовательских Исключений .