Как обрабатывать исключения в Python: подробное визуальное введение

Добро пожаловать! В этой статье вы узнаете, как обрабатывать исключения в Python.

В частности, мы рассмотрим:

  • Исключения
  • Цель обработки исключений
  • Предложение try
  • Предложение за исключением
  • Предложение else
  • Окончательное предложение
  • Как поднять исключения

Вы готовы? Давайте начнем! 😀

1️⃣ Введение в исключения

Начнем с исключений:

  • Какие они?
  • Почему они актуальны?
  • Зачем вам с ними обращаться?

Согласно документации Python:

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

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

Вы, наверное, видели их во время своих программных проектов.

Если вы когда-либо пытались разделить на ноль в Python, вы, должно быть, видели это сообщение об ошибке:

>>> a = 5/0 Traceback (most recent call last): File "", line 1, in  a = 5/0 ZeroDivisionError: division by zero

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

>>> a = "Hello, World" >>> a[456] Traceback (most recent call last): File "", line 1, in  a[456] IndexError: string index out of range

Это примеры исключений.

🔹 Общие исключения

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

  • IndexError - возникает, когда вы пытаетесь проиндексировать список, кортеж или строку за допустимые границы. Например:
>>> num = [1, 2, 6, 5] >>> num[56546546] Traceback (most recent call last): File "", line 1, in  num[56546546] IndexError: list index out of range
  • KeyError - возникает, когда вы пытаетесь получить доступ к значению ключа, которого нет в словаре. Например:
>>> students = {"Nora": 15, "Gino": 30} >>> students["Lisa"] Traceback (most recent call last): File "", line 1, in  students["Lisa"] KeyError: 'Lisa'
  • NameError - возникает, когда имя, на которое вы ссылаетесь в коде, не существует. Например:
>>> a = b Traceback (most recent call last): File "", line 1, in  a = b NameError: name 'b' is not defined
  • TypeError - возникает, когда операция или функция применяется к объекту несоответствующего типа. Например:
>>> (5, 6, 7) * (1, 2, 3) Traceback (most recent call last): File "", line 1, in  (5, 6, 7) * (1, 2, 3) TypeError: can't multiply sequence by non-int of type 'tuple'
  • ZeroDivisionError - возникает при попытке деления на ноль.
>>> a = 5/0 Traceback (most recent call last): File "", line 1, in  a = 5/0 ZeroDivisionError: division by zero

💡 Советы: Чтобы узнать больше о других типах встроенных исключений, обратитесь к этой статье в документации Python.

🔸 Анатомия исключения

Я уверен, что вы заметили в этих сообщениях об ошибках общую картину. Давайте разберем их общую структуру по частям:

Сначала находим эту линию (см. Ниже). Отслеживающий в основном список детализации вызовов функций , которые были сделаны до того , как исключение было поднято.

Отслеживание помогает вам в процессе отладки, потому что вы можете проанализировать последовательность вызовов функций, которые привели к исключению:

Traceback (most recent call last):

Затем мы видим эту строку (см. Ниже) с путем к файлу и строку, вызвавшую исключение. В этом случае путь был оболочкой Python, поскольку пример выполнялся непосредственно в IDLE.

File "", line 1, in  a - 5/0

💡 Совет: если строка, в которой возникло исключение, принадлежит функции, она заменяется именем функции.

Наконец, мы видим описательное сообщение с подробным описанием типа исключения и дополнительной информацией, которая поможет нам отладить код:

NameError: name 'a' is not defined

2️⃣ Обработка исключений: цель и контекст

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

🔹 Пример: ввод пользователя

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

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

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

Perhaps you could display a descriptive message asking the user to enter a valid input, or you could provide a default value for the input. Depending on the context, you can choose what to do when this happens, and this is the magic of error handling. It can save the day when unexpected things happen. ⭐️

🔸 What Happens Behind the Scenes?

Basically, when we handle an exception, we are telling the program what to do if the exception is raised. In that case, the "alternative" flow of execution will come to the rescue. If no exceptions are raised, the code will run as expected.

3️⃣ Time to Code: The try ... except Statement

Now that you know what exceptions are and why you should we handle them, we will start diving into the built-in tools that the Python languages offers for this purpose.

First, we have the most basic statement: try ... except.

Let's illustrate this with a simple example. We have this small program that asks the user to enter the name of a student to display his/her age:

students = {"Nora": 15, "Gino": 30} def print_student_age(): name = input("Please enter the name of the student: ") print(students[name]) print_student_age()

Notice how we are not validating user input at the moment, so the user might enter invalid values (names that are not in the dictionary) and the consequences would be catastrophic because the program would crash if a KeyError is raised:

# User Input Please enter the name of the student: "Daniel" # Error Message Traceback (most recent call last): File "", line 15, in  print_student_age() File "", line 13, in print_student_age print(students[name]) KeyError: '"Daniel"'

🔹 Syntax

We can handle this nicely using try ... except. This is the basic syntax:

In our example, we would add the try ... except statement within the function. Let's break this down piece by piece:

students = {"Nora": 15, "Gino": 30} def print_student_age(): while True: try: name = input("Please enter the name of the student: ") print(students[name]) break except: print("This name is not registered") print_student_age()

If we "zoom in", we see the try ... except statement:

try: name = input("Please enter the name of the student: ") print(students[name]) break except: print("This name is not registered")
  • When the function is called, the try clause will run. If no exceptions are raised, the program will run as expected.
  • But if an exception is raised in the try clause, the flow of execution will immediately jump to the except clause to handle the exception.

💡 Note: This code is contained within a while loop to continue asking for user input if the value is invalid. This is an example:

Please enter the name of the student: "Lulu" This name is not registered Please enter the name of the student: 

This is great, right? Now we can continue asking for user input if the value is invalid.

At the moment, we are handling all possible exceptions with the same except clause. But what if we only want to handle a specific type of exception? Let's see how we could do this.

🔸 Catching Specific Exceptions

Since not all types of exceptions are handled in the same way, we can specify which exceptions we would like to handle with this syntax:

This is an example. We are handling the ZeroDivisionError exception in case the user enters zero as the denominator:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except ZeroDivisionError: print("Please enter a valid denominator.") divide_integers()

Это будет результат:

# First iteration Please enter the numerator: 5 Please enter the denominator: 0 Please enter a valid denominator. # Second iteration Please enter the numerator: 5 Please enter the denominator: 2 2.5

Мы с этим справляемся правильно. Но ... если возникает исключение другого типа, программа не справится с ним корректно.

Здесь у нас есть пример ValueError, потому что одно из значений является float, а не int:

Please enter the numerator: 5 Please enter the denominator: 0.5 Traceback (most recent call last): File "", line 53, in  divide_integers() File "", line 47, in divide_integers b = int(input("Please enter the denominator: ")) ValueError: invalid literal for int() with base 10: '0.5'

Мы можем настроить обработку различных типов исключений.

🔹 Несколько исключений

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

Согласно документации Python:

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

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

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except ZeroDivisionError: print("Please enter a valid denominator.") except ValueError: print("Both values have to be integers.") divide_integers() 

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

🔸 Множественные исключения, одно исключение

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

Согласно документации Python:

Предложение except может называть несколько исключений в виде кортежа в скобках.

Это пример, в котором мы перехватываем два исключения (ZeroDivisionError и ValueError) с одним и тем же exceptпредложением:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except (ZeroDivisionError, ValueError): print("Please enter valid integers.") divide_integers()

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

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers.
Please enter the numerator: 0.5 Please enter valid integers. Please enter the numerator: 

🔹 Обработка исключений, вызванных функциями, вызванными в предложении try

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

Согласно документации Python:

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

Давайте посмотрим на пример, чтобы проиллюстрировать это:

def f(i): try: g(i) except IndexError: print("Please enter a valid index") def g(i): a = "Hello" return a[i] f(50)

У нас есть fфункция и gфункция. fвызывает gв предложении try. С аргументом 50 gвызовет IndexError, потому что индекс 50 недопустим для строки a.

But g itself doesn't handle the exception. Notice how there is no try ... except statement in the g function. Since it doesn't handle the exception, it "sends" it to f to see if it can handle it, as you can see in the diagram below:

Since f does know how to handle an IndexError, the situation is handled gracefully and this is the output:

Please enter a valid index

💡 Note: If f had not handled the exception, the program would have ended abruptly with the default error message for an IndexError.

🔸 Accessing Specific Details of Exceptions

Exceptions are objects in Python, so you can assign the exception that was raised to a variable. This way, you can print the default description of the exception and access its arguments.

Согласно документации Python:

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

Здесь у нас есть пример (см. Ниже), в котором мы назначаем экземпляр ZeroDivisionErrorпеременной e. Затем мы можем использовать эту переменную в предложении except для доступа к типу исключения, его сообщению и аргументам.

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) # Here we assign the exception to the variable e except ZeroDivisionError as e: print(type(e)) print(e) print(e.args) divide_integers()

Соответствующий вывод будет:

Please enter the numerator: 5 Please enter the denominator: 0 # Type  # Message division by zero # Args ('division by zero',)

💡 Совет: если вы знакомы со специальными методами, согласно документации Python: «для удобства экземпляр исключения определяет, __str__()что аргументы могут быть напечатаны напрямую без ссылки .args».

4. Теперь давайте добавим: пункт "else"

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

Согласно документации Python:

У оператора tryexceptесть необязательное предложение else , которое, если оно присутствует, должно следовать за всеми предложениями за исключением. Это полезно для кода, который должен выполняться, если предложение try не вызывает исключение.

Вот пример использования elseпредложения:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) result = a / b except (ZeroDivisionError, ValueError): print("Please enter valid integers. The denominator can't be zero") else: print(result) divide_integers()

Если не возникает никаких исключений, выводится результат:

Please enter the numerator: 5 Please enter the denominator: 5 1.0

Но если возникает исключение, результат не печатается:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers. The denominator can't be zero

💡 Совет: согласно документации Python:

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

5️⃣ Пункт "окончательно"

Предложение finally - последнее предложение в этой последовательности. Это необязательно , но если вы включите его, оно должно быть последним предложением в последовательности. finallyОговорка всегда выполняются, даже если исключение было поднято в предложении Try.  

Согласно документации Python:

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

Предложение finally обычно используется для выполнения действий по «очистке», которые всегда должны выполняться. Например, если мы работаем с файлом в предложении try, нам всегда нужно будет закрыть файл, даже если при работе с данными возникло исключение.

Вот пример предложения finally:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) result = a / b except (ZeroDivisionError, ValueError): print("Please enter valid integers. The denominator can't be zero") else: print(result) finally: print("Inside the finally clause") divide_integers()

Это результат, когда не возникало никаких исключений:

Please enter the numerator: 5 Please enter the denominator: 5 1.0 Inside the finally clause

Это результат, когда возникло исключение:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers. The denominator can't be zero Inside the finally clause

Обратите внимание , как finallyусловие всегда выполняется.

❗️Important: remember that the else clause and the finally clause are optional, but if you decide to include both, the finally clause has to be the last clause in the sequence.

6️⃣ Raising Exceptions

Now that you know how to handle exceptions in Python, I would like to share with you this helpful tip: you can also choose when to raise exceptions in your code.

This can be helpful for certain scenarios. Let's see how you can do this:

This line will raise a ValueError with a custom message.

Here we have an example (see below) of a function that prints the value of the items of a list or tuple, or the characters in a string. But you decided that you want the list, tuple, or string to be of length 5. You start the function with an if statement that checks if the length of the argument data is 5. If it isn't, a ValueError exception is raised:

def print_five_items(data): if len(data) != 5: raise ValueError("The argument must have five elements") for item in data: print(item) print_five_items([5, 2])

The output would be:

Traceback (most recent call last): File "", line 122, in  print_five_items([5, 2]) File "", line 117, in print_five_items raise ValueError("The argument must have five elements") ValueError: The argument must have five elements

Notice how the last line displays the descriptive message:

ValueError: The argument must have five elements

You can then choose how to handle the exception with a try ... except statement. You could add an else clause and/or a finally clause. You can customize it to fit your needs.

🔹 Helpful Resources

  • Exceptions
  • Handling Exceptions
  • Defining Clean-up Actions

I hope you enjoyed reading my article and found it helpful. Now you have the necessary tools to handle exceptions in Python and you can use them to your advantage when you write Python code. ? Check out my online courses. You can follow me on Twitter.

⭐️ You may enjoy my other freeCodeCamp /news articles:

  • The @property Decorator in Python: Its Use Cases, Advantages, and Syntax
  • Data Structures 101: Graphs — A Visual Introduction for Beginners
  • Data Structures 101: Arrays — A Visual Introduction for Beginners