Skip to content

5. Винятки

Тепер розглянемо винятки.

Image

5.1. скрипт [exceptions_01]

Перший скрипт ілюструє необхідність обробки винятків.

# навмисно викликаємо помилку
x = 4 / 0

Ми навмисно викликаємо помилку, щоб побачити, що відбудеться (exceptions-01.py):

1
2
3
4
5
6
7
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py", line 2, in <module>
    x = 4 / 0
ZeroDivisionError: division by zero

Process finished with exit code 1

У рядку 4 ми бачимо:

  • тип винятку: ZeroDivisionError;
  • відповідне повідомлення про помилку: division by zero. Воно англійською мовою. Це те, що, можливо, варто змінити.

Основне правило полягає в тому, що слід робити все можливе, щоб уникнути винятків, які генерує інтерпретатор Python. Ми повинні обробляти помилки самостійно.

Синтаксис обробки винятків такий:


try:
    actions susceptibles de lancer une exception
except (Ex1, Ex2…) as ex:
    actions de gestion de l'exception [ex]
except (Ex11, Ex12…) as ex:
    actions de gestion de l'exception [ex]
finally:
   actions toujours exécutées qu'il y ait exception ou non

У блоці try виконання дій зупиняється, щойно виникає виняток (помилка). У цьому випадку виконання продовжується з дій одного з блоків except:

  • рядок 3: якщо виняток [ex], що виник, належить до типу, який входить до кортежу (Ex1, Ex2…) або є похідним від одного з них, то виконуються дії з рядка 4;
  • рядок 5: якщо виняток не був перехоплений рядком 3, і існує інший блок [except], то відбувається той самий процес. І т. д.;
  • може бути стільки умов [except], скільки потрібно для обробки різних типів винятків, що можуть виникнути в [try];
  • якщо виняток не було оброблено жодним із блоків [except], то він передається до коду, що його викликав. Якщо цей код сам знаходиться у структурі try / except, виняток знову обробляється; інакше він продовжує передаватися вгору по ланцюжку викликаних методів. В кінцевому підсумку вона потрапляє до інтерпретатора Python. Той зупиняє виконувану програму та виводить повідомлення про помилку, подібне до наведеного у попередньому прикладі. Отже, правило полягає в тому, що головна програма повинна перехоплювати всі винятки, які можуть передаватися з викликаних методів;
  • рядок 7: оператор [finally] завжди виконується незалежно від того, чи сталося виключення (продовження блоку except**), чи ні (продовження блоку try**). Це справедливо навіть у тому випадку, якщо виключення сталося, але не було перехоплене. У цьому випадку оператор [finally] буде виконано до того, як виняток буде передано до коду, що викликає;

Виняток містить інформацію про помилку, що сталася. Її можна отримати за допомогою такого синтаксису:


except MyException as exception:

[exception] — це виняток, що стався. [exception.args] представляє кортеж параметрів винятку.

Щоб викликати виняток, використовується синтаксис


raise MyException(param1, param2…)

, де найчастіше MyException є класом, похідним від класу BaseException. Параметри, передані конструктору класу, будуть доступні в блоці except структур перехоплення винятків із синтаксисом [ex.args], якщо [ex] є винятком, перехопленим блоком [except].

Ці поняття проілюстровано в наведеному нижче скрипті.

5.2. скрипт [exceptions_02]

Наступний скрипт явно обробляє помилки:


# обробка винятків
essai = 0

# викликаємо помилку та обробляємо її
x = 2
try:
    x = 4 / 0
except ZeroDivisionError as erreur:
    # помилка є перехопленим винятком
    print(f"essai n° {essai} : {erreur}")
# значення x не змінилося
print(f"x={x}")

# повторюємо
essai += 1
try:
    x = 4 / 0
except BaseException as erreur:
    # перехоплюємо найзагальніше виключення
    # помилка є перехопленим винятком
    print(f"essai n° {essai} : {erreur}")

# можна перехоплювати різні типи винятків
# виконання зупиняється на першому [except], здатному обробити виняток
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # це виключення тут не виникає
    print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
    # перехоплюється найзагальніше виключення
    print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
    # перехоплюється конкретний тип
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")

# починаємо спочатку, змінивши порядок 
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # це виключення тут не виникає
    print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
    # перехоплюється конкретний тип
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
    # перехоплюється найзагальніше виключення
    print(f"essai n° {essai} : (Exception) {erreur}")

# виключення без аргументів
essai += 1
try:
    x = 4 / 0
except:
    # характер винятку не має значення
    print(f"essai n° {essai} : il y a eu un problème")

# інший тип винятку
essai += 1
try:
    # x не можна перетворити на ціле число
    x = int("x")
except ValueError as erreur:
    # помилка є перехопленим винятком
    print(f"essai n° {essai} : {erreur}")

# виняток передає інформацію у кортежі, доступному для програми
essai += 1
try:
    x = int("x")
except ValueError as erreur:
    # помилка є перехопленим винятком
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# винятки можна генерувати
essai += 1
try:
    raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
    # помилка — це перехоплене виключення
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")


# можна створювати власні винятки
# вони повинні походити від класу [BaseException]
class MyError(BaseException):
    pass


# викликається виняток MyError
essai += 1
try:
    raise MyError("info1", "info2", "info3")
except MyError as erreur:
    # помилка є перехопленим винятком
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# викликається виняток MyError із повідомленням про помилку
essai += 1
try:
    raise MyError("mon msg d'erreur")
except MyError as erreur:
    # помилка — це перехоплене виключення
    print(f"essai n° {essai} : {erreur.args[0]}")

# блока finally завжди виконується
# незалежно від того, чи виникло виключення
essai += 1
x = None
try:
    x = 1
except:
    # виняток
    print(f"essai n° {essai} : exception")
finally:
    # виконується в будь-якому випадку
    print(f"essai n° {essai} : finally x={x}")

essai += 1
x = None
try:
    x = 2 / 0
except:
    # виняток
    print(f"essai n° {essai} : exception")
finally:
    # виконується в будь-якому випадку
    print(f"essai n° {essai} : finally x={x}")

# не обов'язково вказувати клаузулу [except]
essai += 1
try:
    # викликає помилку
    x = 4 / 0
finally:
    # виконується в будь-якому випадку
    print(f"essai n° {essai} : finally x={x}")

Примітки:

  • рядки 4–12: обробляється поділ на нуль;
  • рядок 8: перехоплюється саме те виключення, яке виникло, і виводиться на екран;
  • рядок 12: через виникнення винятку x не отримав значення з рядка 7 і, отже, не змінив свого значення;
  • рядки 14–21: повторюємо те саме, але перехоплюємо виняток вищого рівня типу BaseException. Оскільки виняток ZeroDivisionError походить від класу BaseException, оператор except зупинить його;
  • рядки 23–36: додаємо кілька умов except для обробки різних типів винятків. Буде виконана лише одна умова except або жодна, якщо виняток не відповідає жодній умові except;
  • рядки 38–50: повторюємо те саме, змінюючи порядок клаузул [except], щоб продемонструвати їхню роль;
  • рядки 52–58: клаузула except може не мати жодного аргументу. У цьому випадку вона зупиняє всі винятки;
  • рядки 60–67: вводиться виняток ValueError;
  • рядки 69–75: отримується інформація, що передається винятком;
  • рядки 77–83: описують спосіб виклику (raise) винятку;
  • рядки 86–106: ілюструють використання власного класу винятків MyError. Клас MyError просто походить від базового класу BaseException. Він нічого не додає до свого базового класу. Але тепер його можна явно вказати в операторах except;
  • рядки 108–130: ілюструють використання оператора finally;
  • рядки 132–139: ці рядки показують, що клаузула [except] не є обов’язковою;

Результати на екрані такі:

C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py", line 136, in <module>
    x = 4 / 0
ZeroDivisionError: division by zero
essai n° 0 : division by zero
x=2
essai n° 1 : division by zero
essai n° 2 : (Exception) division by zero
essai n° 3 : (ZeroDivisionError) division by zero
essai n° 4 : il y a eu un problème
essai n° 5 : invalid literal for int() with base 10: 'x'
essai n° 6 : invalid literal for int() with base 10: 'x', paramètres=("invalid literal for int() with base 10: 'x'",)
essai n° 7 : ('param1', 'param2', 'param3'), paramètres=('param1', 'param2', 'param3')
essai n° 8 : ('info1', 'info2', 'info3'), paramètres=('info1', 'info2', 'info3')
essai n° 9 : mon msg d'erreur
essai n° 10 : finally x=1
essai n° 11 : exception
essai n° 11 : finally x=None
essai n° 12 : finally x=None

Process finished with exit code 1

5.3. скрипт [exceptions_03]

Цей новий скрипт ілюструє передачу винятків у ланцюжку викликів функцій:


# власне виняток
class MyError(BaseException):
    pass


# три функції
def f1(x: int) -> int:
    # винятки не обробляються — вони автоматично передаються вище
    return f2(x)


def f2(y: int) -> int:
    # винятки не обробляються — вони автоматично передаються вище
    return f3(y)


def f3(z: int) -> int:
    if (z % 2) == 0:
        # якщо z парне, генерується виняток
        raise MyError("exception dans f3")
    else:
        return 2 * z


# ---------- main

# винятки передаються вгору по ланцюжку викликаних методів
# доти, доки якийсь метод не перехопить його. У цьому випадку це буде main
try:
    print(f1(4))
except MyError as erreur:
    print(f"type : {type(erreur)}, arguments : {erreur.args}")


# ще три функції, які доповнюють винятки, які вона передає далі
def f4(x: int) -> int:
    try:
        return f5(x)
    except MyError as erreur:
        # виняток доповнюється, а потім перекидається
        raise MyError("exception dans f4", erreur)


def f5(y: int) -> int:
    try:
        return f6(y)
    except MyError as erreur:
        # збагачуємо виняток, а потім перекидаємо його
        raise MyError("exception dans f5", erreur)


def f6(z: int) -> int:
    if (z % 2) == 0:
        # генерується виняток, якщо z парне
        raise MyError("exception dans f6")
    else:
        return 2 * z


# ---------- main
try:
    print(f4(4))
except MyError as erreur:
    # виведення інформації про виняток
    print(f"type : {type(erreur)}, arguments : {erreur.args}")
    # можна простежити стек винятків
    err = erreur
    # виводиться повідомлення про помилку
    print(err.args[0])
    # чи є виняток інкапсульованим?
    while len(err.args) == 2 and isinstance(err.args[1], BaseException):
        # зміна винятку
        err = err.args[1]
        # перший аргумент — це повідомлення про помилку
        print(err.args[0])

Примітки:

  • у рядках 25–32, під час виклику main --> f1 --> f2 --> f3 (рядок 30), виняток MyError, згенерований функцією f3, буде передаватися вгору до функції main. Потім вона буде оброблена за допомогою оператора except у рядку 31;
  • рядки 61–75: під час виклику main → f4 → f5 → f6 (рядок 62) виняток MyError, згенерований функцією f6, буде передаватися вгору до функції main. Потім вона буде оброблена за допомогою оператора except у рядку 63. Цього разу, під час проходження ланцюжка викликів функцій, виняток MyError, що просувається вгору, сам інкапсульований в інший виняток;
  • рядки 66–75: показують, як просуватися вгору по стеку винятків;
  • рядок 71: функція [isinstance(instance, Classe)] повертає True, якщо об’єкт [instance] є типом [Classe] або його похідним. Тут ми використали виняток найвищого рівня [BaseException], що гарантує нам отримання всіх винятків;

Результати виведення на екран такі:

1
2
3
4
5
6
7
8
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_03.py
type : <class '__main__.MyError'>, arguments : ('exception dans f3',)
type : <class '__main__.MyError'>, arguments : ('exception dans f4', MyError('exception dans f5', MyError('exception dans f6')))
exception dans f4
exception dans f5
exception dans f6

Process finished with exit code 0