Як обробляти винятки в Python: Детальний візуальний вступ

Ласкаво просимо! У цій статті ви дізнаєтеся, як обробляти винятки в Python.

Зокрема, ми розглянемо:

  • Винятки
  • Мета обробки винятків
  • Застереження про спробу
  • Застереження "виключення"
  • Застереження 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
  • Помилка TypeE - виникає, коли операція або функція застосовується до об'єкта невідповідного типу. Наприклад:
>>> (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 допомагає вам під час процесу налагодження, оскільки ви можете проаналізувати послідовність викликів функцій, що призвели до винятку:

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 може містити більше одного, крім речення , щоб вказати обробники для різних винятків. Буде виконано щонайбільше один обробник .

У цьому прикладі ми маємо два, крім речення. Один з них обробляє 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() 

💡 Порада: Ви повинні визначити , які типи винятків можуть бути підняті в блоці спробувати обробити їх відповідним чином .

🔸 Кілька винятків, один пункт

Ви також можете обрати різні типи винятків з одним і тим же пунктом, крім.

Відповідно до документації Python:

Застереження за винятком може називати кілька винятків як кортеж у дужках.

Це приклад, коли ми ловимо два винятки (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()

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

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 іншої функції, а сама функція не обробляє її, виклик обробляє її, якщо є відповідне речення, окрім речення.

Відповідно до документації 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:

Застереження "виключення" може вказувати змінну після імені винятку . Змінна прив’язана до екземпляра винятку з аргументами, що зберігаються в instance.args.

Тут ми маємо приклад (див. Нижче), коли ми призначали екземпляр ZeroDivisionErrorзмінної e. Тоді ми можемо використовувати цю змінну в реченні exclude для доступу до типу винятку, його повідомлення та аргументів.

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

Відповідно до документації Python:

Якщо finallyречення присутнє, finallyречення буде виконуватися як останнє завдання до завершення tryоператора. finallyПункт працює чи не tryвиробляє заяву виключення.

Заключне положення, як правило, використовується для виконання "очисних" дій, які слід завжди виконувати. Наприклад, якщо ми працюємо з файлом у реченні try, нам завжди потрібно буде закрити файл, навіть якщо під час роботи з даними було викликано виняток.

Ось приклад пропозиції з завершення:

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