Перейти до змісту

Робота з винятками try/except/else/finally

try/except

Якщо ви повторювали приклади, які використовувалися раніше, то, напевно, були ситуації, коли виникала помилка. Швидше за все, це була помилка синтаксису, коли не вистачало, наприклад, двокрапки. Як правило, Python досить зрозуміло реагує на такі помилки, і їх можна виправити.

Проте, навіть коли код написаний синтаксично правильно, можуть виникати помилки. У Python ці помилки називаються винятки (exceptions).

Приклади винятків:

In [1]: 2/0
-----------------------------------------------------
ZeroDivisionError: division by zero

In [2]: 'test' + 2
-----------------------------------------------------
TypeError: must be str, not int

У цьому випадку виникли два винятки: ZeroDivisionError та TypeError.

Найчастіше можна передбачити, які винятки виникнуть під час виконання програми. Наприклад, якщо програма на вхід очікує два числа, а на виході видає їх суму, а користувач ввів замість одного з чисел рядок, з'явиться помилка TypeError, як у прикладі вище.

Python дозволяє працювати з винятками. Їх можна перехоплювати і виконувати певні дії у разі, якщо виник виняток.

Для роботи з винятками використовується конструкція try/except:

In [3]: try:
   ...:     2/0
   ...: except ZeroDivisionError:
   ...:     print("You can't divide by zero")
   ...:
You can't divide by zero

Конструкція try працює таким чином:

  • спочатку виконуються вирази, які записані в блоці try
  • якщо під час виконання блоку try не виникло жодних винятків, блок except пропускається, і виконується подальший код
  • якщо під час виконання блоку try в якомусь місці виник виняток, частина блоку try, що залишилася, пропускається
  • якщо в блоці except зазначено виняток, який виник, виконується код у блоці except
  • якщо виняток, який виник, не вказано в блоці except, виконання програми переривається та генерується виняток

Note

Коли у програмі виникає виняток, вона одразу завершує роботу.

Зверніть увагу, що рядок Cool! у блоці try не виводиться:

In [4]: try:
   ...:     print("Let's divide some numbers")
   ...:     2/0
   ...:     print('Cool!')
   ...: except ZeroDivisionError:
   ...:     print("You can't divide by zero")
   ...:
Let's divide some numbers
You can't divide by zero

У конструкції try/except може бути багато except, якщо потрібні різні дії, залежно від типу помилки.

Наприклад, скрипт divide.py ділить два числа введених користувачем:

num1 = input("Введіть перше число: ")
num2 = input("Введіть друге число: ")

try:
    print("Результат: ", int(num1) / int(num2))
except ValueError:
    print("Вводьте лише числа")
except ZeroDivisionError:
    print("На нуль ділити не можна")

Приклади виконання скрипту:

$ python divide.py
Введіть перше число: 3
Введіть друге число: 1
Результат:  3

$ python divide.py
Введіть перше число: 5
Введіть друге число: 0
На нуль ділити не можна

$ python divide.py
Введіть перше число: qewr
Введіть друге число: 3
Вводьте лише числа

У цьому випадку виняток ValueError виникає, коли користувач ввів рядок замість числа під час переведення рядка в число. Виняток ZeroDivisionError виникає у разі, якщо друге число дорівнювало 0.

Якщо немає потреби виводити різні повідомлення на помилки ValueError та ZeroDivisionError, можна зробити так:

num1 = input("Введіть перше число: ")
num2 = input("Введіть друге число: ")

try:
    print("Результат: ", int(num1)/int(num2))
except (ValueError, ZeroDivisionError):
    print("Щось пішло не так...")

Перевірка:

$ python divide_ver2.py
Введіть перше число: wer
Введіть друге число: 4
Щось пішло не так...

$ python divide_ver2.py
Введіть перше число: 5
Введіть друге число: 0
Щось пішло не так...

Warning

У блоці except можна не вказувати конкретний виняток або винятки. У такому разі перехоплюватимуться майже всі винятки. Це робити не рекомендується!

try/except/else

У конструкції try/except є опціональний блок else. Він виконується у тому випадку, якщо не було винятку.

Наприклад, якщо необхідно виконувати надалі якісь операції з даними, які ввів користувач, можна записати їх у блоці else:

num1 = input("Введіть перше число: ")
num2 = input("Введіть друге число: ")
try:
    result = int(num1)/int(num2)
except (ValueError, ZeroDivisionError):
    print("Щось пішло не так...")
else:
    print("Результат: ", result)

Приклад виконання:

$ python divide_ver3.py
Введіть перше число: 10
Введіть друге число: 2
Результат:  5

$ python divide_ver3.py
Введіть перше число: werq
Введіть друге число: 3
Щось пішло не так...

try/except/finally

Блок finally – це ще один опціональний блок у конструкції try. Він виконується завжди, незалежно від того, чи був виняток чи ні.

Сюди ставляться дії, які треба виконати у будь-якому випадку. Наприклад, це може бути закриття файлу, вивільнення якихось ресурсів.

Приклад із блоком finally:

num1 = input("Введіть перше число: ")
num2 = input("Введіть друге число: ")
try:
    result = int(num1)/int(num2)
except (ValueError, ZeroDivisionError):
    print("Щось пішло не так...")
else:
    print("Результат: ", result**2)
finally:
    print("The End")

Перевірка:

$ python divide_ver4.py
Введіть перше число: 10
Введіть друге число: 2
Результат:  5
The End

$ python divide_ver4.py
Введіть перше число: qwerewr
Введіть друге число: 3
Щось пішло не так...
The End

$ python divide_ver4.py
Введіть перше число: 4
Введіть друге число: 0
Щось пішло не так...
The End

Коли використовувати винятки

Як правило, той самий код можна написати і з використанням винятків, і без них. Наприклад, цей варіант коду:

while True:
    num1 = input("Введіть перше число: ")
    num2 = input("Введіть друге число: ")
    try:
        result = int(num1)/int(num2)
    except ValueError:
        print("Підтримуються лише числа")
    except ZeroDivisionError:
        print("На нуль ділити не можна")
    else:
        print(result)
        break

Можна переписати код таким чином без try/except:

while True:
    num1 = input("Введіть перше число: ")
    num2 = input("Введіть друге число: ")
    if a.isdigit() and b.isdigit():
        if int(num2) == 0:
            print("На нуль ділити не можна")
        else:
            print(int(num1)/int(num2))
            break
    else:
        print("Підтримуються лише числа")

Не завжди аналогічний варіант без використання винятків буде простим і зрозумілим.

Важливо в кожній конкретній ситуації оцінювати, який варіант коду зрозуміліший, компактніший і універсальніший - з винятками або без.

Якщо ви раніше використовували якусь іншу мову програмування, є ймовірність, що використання винятків вважалося поганим тоном. У Python це не так.

raise

Іноді в коді треба згенерувати виняток, це можна зробити так:

raise ValueError("При виконанні команди виникла помилка")

Вбудовані винятки

У Python є багато вбудованих винятків, кожне з яких генерується у певній ситуації.

Наприклад, TypeError зазвичай генерується, коли очікувався один тип даних, а передали інший

In [1]: "a" + 3
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-5aa8a24e3e06> in <module>
----> 1 "a" + 3
TypeError: can only concatenate str (not "int") to str

ValueError коли тип вірний, але значення не відповідає очікуваному:

In [2]: int("a")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-d9136db7b558> in <module>
----> 1 int("a")
ValueError: invalid literal for int() with base 10: 'a'