НМЦ

Програмування мовою Python: основи та практика

Електронний посібник

ВФПО

ТЕМА 4. ВИНЯТКИ ТА ЇХ ОБРОБКА

 

 

 

 

Уроки Python з нуля / #4 – Обробка винятків.

Конструкція "tryexcept"

 

Презентація. Винятки та їх обробка №4

 

4. Винятки та їх обробка

 

Виняток, або exception, ‒ це несподіваний стан, який виникає під час виконання програми і може призвести до помилок.

Наприклад, ділення на нуль, проблеми з читанням файлу чи вичерпання доступної пам’яті ‒ це лише деякі з можливих винятків. В інших мовах програмування потрібно заздалегідь передбачати ці можливі помилки і визначати способи їх обробки. У Python для цього використовується механізм винятків.

 

В контексті Python виняток є типом даних, що містить інформацію про помилку. Також вживається термін «виняткова ситуація», який означає випадок, коли виникає виняток. Часто ці терміни використовують як синоніми.

Помилки, або виняткові ситуації, бувають різні. Зазвичай, виділяють три типи помилок.

Syntax errors. Це вид помилок, з якими ви зіткнетесь першочергово, проте їх дуже легко виправити. Syntax error вказує на порушення «граматики» Python. Python робить все можливе, аби вказати на рядок і символ, де було виявлено помилку. Складність полягає в тому, що іноді він неправильно визначає місце помилки, і вона є десь раніше в програмі. Тому рядок і символ, які Python вказує у syntax error, можуть бути лише початком вашого пошуку.

Logic errors. Це помилка, спричинена неправильним порядком команд чи зв'язком між ними. Наведімо приклад: «Випийте води, покладіть пляшку в рюкзак, дійдіть до бібліотеки, а потім закрийте пляшку».

Semantic errors. Це повідомлення означає, що помилка міститься саме в роботі програми. Тобто, програма написана абсолютно правильно, але вона видає не те, що ви від неї очікували. Це може бути схоже на ситуацію, коли ви описуєте другу дорогу до ресторану і говорите: «Як доїдеш до перехрестя із заправкою, поверни ліворуч і рухайся ще кілометр, рестораном буде червона будівля зліва від тебе». Ваш друг дуже запізнюється і дзвонить вам, аби сказати, що він знаходиться на фермі, де замість ресторану він бачить лише сарай. Ви запитуєте: «На заправці ти повернув ліворуч чи праворуч?», а він відповідає: «Я чітко слідував твоїм вказівкам, у мене вони записані, там сказано повернути ліворуч і проїхати кілометр до заправки». Все, що ви можете йому відповісти, це лише: «Мені дуже шкода, хоч мої поради і були правильно сформульовані синтаксично, та, на жаль, містили непомітну семантичну помилку».

 

Загальна ідея, покладена в основу методу обробки виняткових ситуацій, така: програмний код, у якому теоретично може виникнути помилка, спеціально виділяється – образно кажучи, «береться на контроль». Якщо під час виконання цього програмного коду помилка не виникає, то нічого особливого не відбувається. Якщо під час виконання «контрольованого» коду виникає помилка, то виконання коду зупиняється і автоматично створюється виняток.

 

Коли Python видає помилку чи результат, який відрізняється від того, що ви очікували, починається полювання на причину помилки.

 

Налагодження – це процес пошуку причини помилки у вашому коді.

 

Під час налагодження програми, а особливо якщо ви працюєте над складним багом, варто спробувати зробити це:

v Перевірити. Перегляньте свій код, прочитайте його про себе і перевірте, чи він відповідає тому, що ви хотіли донести;

v Переробити. Поекспериментуйте, внесіть зміни та запустіть різні версії. Часто, зі зміною порядку розташування елементів у програмі проблема стає очевидною, але іноді доводиться витратити деякий час, щоб побудувати основу."*

v Переміркувати. Не спішіть, подумайте трішки! Який це тип помилки? Яку інформацію ви можете отримати з повідомлення про помилку або з результатів роботи програми? Що могло спричинити виникнення проблеми? Що ви змінювали востаннє перед тим, як з'явилася проблема?

v Повернутися. Іноді найкраще відступити, скасовуючи нещодавні зміни доти, доки не повернетеся до моменту, коли програма працює, де ви все розумієте.

 

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

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

Якщо ви не розумієте, що робить ваша програма, ви можете переглянути її хоч 100 разів, а так і не побачите помилку, адже вона у вас в голові. Переробити та внести зміни особливо допомагає, якщо ви проведете невеличке просте випробування вашої програми. Однак, якщо робити це бездумно, не перечитавши код, можна потрапити в ситуацію, яка зветься «блукання в нетрях програми», тобто процес внесення випадкових змін доти, доки програма не працюватиме належним чином. Зрозуміло, що таке блукання може зайняти багато часу. Вам потрібен час, щоб подумати.

 

Налагодження це як експериментальна наука

 

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

Але навіть найкращі методи налагодження не спрацюють, якщо помилок занадто багато, або якщо код, який ви намагаєтеся виправити, занадто великий і складний. Іноді найкращим варіантом є повернення назад, спрощення програми, доки ви не повернетеся до моменту, коли все працює.

Програмісти-початківці часто не хочуть повертатися назад, адже їм не хочеться видаляти рядок коду (навіть якщо він неправильний). Щоб стало легше, скопіюйте свою програму в інший файл, перш ніж починати її видаляти. Отже, ви зможете поступово додавати частини коду назад до програми.

 

 

Розгляньмо як приклад, виняток ділення на нуль. Якщо спробувати виконати операцію, 1/0 то виникне помилка, оскільки на 0 ділити не можна.

Інтерпретатор відреагує на цю помилку генерацією винятку (припиненням подальшого виконання програми) та виведе відповідне повідомлення.

>>> 1/0

Traceback (most recent call last):

File "<pyshell#0> ", line 1, in <module>

1/0

ZeroDivisionError: division by zero

 

Розберімо це повідомлення докладніше:

v  Traceback (most recent call last) – повідомлення про те, що інтер­претатор «зловив» виняток;

v  File "<pyshell#0> ", line 1, in <module> звідки було запущено код програми, в якому виник виняток (це може бути ім’я файлу, вказівка на інтерактивний режим в консоліstdin, вказівка на інтерактивний режим оболонки IDLE - pyshell#0) і номер рядка, в якому це сталося;

v  1/0 – вираз, в якому стався виняток;

v  ZeroDivisionError: division by zero – тип винятку (назва винятку) і його короткий опис.

 

Наведімо приклади деяких інших винятків:

Операція застосована до об’єкта невідповідного типу:

>>> 2 + ‘1’

Traceback (most recent call last):

File "<pyshell#0> ", line 1, in <module>

2 + ‘1’

TypeError: unsupported operand type(s) for +: ‘int’ and ‘str’

 

Функція отримує аргумент правильного типу, але некоректного значення:

>>> int(‘qwerty’)

Traceback (most recent call last):

File "<pyshell#0> ", line 1, in <module>

int(‘qwerty’)

ValueError: invalid literal for int() with base 10:

qwerty

 

Синтаксична помилка:

>>> t:=3

File "<pyshell#0> ", line 1, in <module>

t:=3

^

SyntaxError: invalid syntax

 

У цих прикладах генеруються винятки: TypeError, ValueError, SyntaxError.

 

Помилки можна не лише перехоплювати, а й генерувати штучно. Робиться це за допомогою інструкцій raise або assert. Як це робиться й навіщо, ми обговоримо в останньому розділі – після того, як познайомимося з класами й об’єктами. Там буде описано, як можна створювати власні класи винятків

 

4.2. Обробка винятків

 

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

 

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

Якщо передбачити місця та умови виникнення винятків, можна попередньо налаштувати їх обробку. Для цього використовується конструкція try-except, яка має кілька форм. У цій конструкції можуть бути використані різні оператори: try, except, else, finally, raise. Їх використання має свої відповідні особливості.

 

Найпростіша форма конструкції try-except для обробки винятку має такий вигляд:

#Код блоку try (код, в якому може виник­нути виняток)

except Назва_винятку:

#Код блоку except (код, що виконується при вказаному винятку)

Конструкція try except виконується за такою послідовністю кроків.

 

   Спочатку виконується код блоку try, що містить вирази або вирази між ключовими словами try і except.

   Якщо під час виконання коду блоку try не виникло винятку, програма пропускає блок except і виконання конструкції tryexcept завершується.

   У разі виникнення винятку під час виконання коду блоку try, виконання цього блоку припиняється і управління переходить до обробника except.

   Якщо тип винятку збігається з назвою винятку, вказаною після ключового слова except, виконується відповідний блок except. Після цього виконується код після конструкції tryexcept.

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

 

Незважаючи на те, що у цьому варіанті конструкції tryexcept вказано лише одну назву винятку, фактично будуть оброблені як сам вказаний виняток, так і всі його нащадки. Наприклад, у разі перехоплення винятку ArithmeticError також будуть оброблені винятки FloatingPointError, OverflowError, ZeroDivisionError.

Якщо потрібно обробити кілька винятків, які не є у ієрархічних зв’язках, можна вказати їх назви в розділених комами дужках у блоці except.

Наприклад:

try:

#Код блоку try (код, в якому можуть виникнути винятки)

except (RuntimeError, TypeError, NameError):

#Код блоку except (код, що виконується за вказаних винятків)

Також можна використовувати ключове слово except без конкретизації типів винятків. Цей вид обробника перехоплює всі винятки, включаючи системні, як-от переривання з клавіатури або системний вихід. Однак такий спосіб майже не використовується через широкий спектр перехоплюваних винятків. Якщо потрібно обробити всі несистемні винятки, можна скористатися except Exception.

Крім того, у разі, коли потрібно різним чином обробляти різні винятки в межах певного оператора або набору операторів, у конструкції tryexcept можна розмістити кілька блоків except для відповідних винятків. У разі виникнення винятку будуть переглянуті блоки except почергово, зверху донизу, для знаходження відповідного обробника (тільки перший придатний блок except буде виконаний).

 

Узагальнюючи вищевикладене, конструкцію tryexcept можна навести так:

try:

#Код блоку try except Назва_винятку1:

#Код блоку, що виконується за вказаних винятків except (Назва_винятку2, Назва_винятку3, …):

#Код блоку, що виконується за вказаних винятків

. . .

except Exception:

#Код блоку, що виконується за будь-якого несистемного винятку, якого не було оброблено

Але й на цьому етапі вказана конструкція tryexcept не є повною. Конструкція tryexcept може мати ще два блоки: finally та else. Код блоку finally виконується в будь-якому випадку, незалежно від того, чи виник виняток в блоці try, чи ні. Код блоку else виконується в тому випадку, якщо винятку в блоці try не було. Блок else досить добре описує частину дерева розв’язку: «Якщо цього виконати не можна, то (інакше) виконати це».

Якщо блок else є, то він має йти після всіх блоків except, але до блоку finally.

try:

#Код блоку try except Назва_винятку1:

#Код блоку, що виконується за вказаних винятків except (Назва_винятку2, Назва_винятку3, …):

#Код блоку, що виконується за вказаних винятків

. . .

except Exception:

#Код блоку, що виконується за будь-якого несистемного винятку, якого не було оброблено

else:

#Код блоку, що виконується, якщо не було винятків finally:

#Код блоку, що виконується в будь-якому випадку, можливо після відповідного блоку except

 

Для прикладу напишемо програму, в якій для введеного числа X буде знаходитися частка 1/X. Без опрацювання винятків ця програма матиме такий вигляд:

x=int(input()) k=1/x

print(k)

 

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

v x=int(input()) – якщо користувач введе не ціле число, то функція int() не зможе введене значення привести до цілого і виникне виняток ValueError.

v k=1/x – якщо користувач введе 0, то інтерпретатору не вдасться виконати ділення на 0, і виникне виняток ZeroDivisionError.

 

Доповнивши програму блоком опрацювання винятку, отримаємо:

try:

x=int(input()) k=1/x

except ValueError:

print(‘Помилка: очікувалося ціле число.’)

except ZeroDivisionError:

print(‘Помилка: ділення на ноль.’) else:

print(k)

 

Тобто у разі виникнення винятку буде виведене відповідне повідомлення про помилку.

 

 

Іноді під час обробки винятків може бути досить просто вивести системний опис самого винятку. У таких випадках може бути зручно просто обробити виняток Exception, повертаючи опис саме того винятку, який став причиною виникнення помилки та є нащадком Exception.

 

Для цього можна прив’язати виняток, який виник, до змінної, використовуючи оператор as:

except Exception  as <імя змінної>

 

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

Наприклад, нашу попередню задачу перепишемо:

try:

    x=int(input())

    k=1/x

except Exception  as mes:

    print(‘Помилка’, type(mes),’:’, mes)

else:

    print(k)

Якщо користувач введе не ціле число (наприклад, 33.4), то отримає повідомлення:

Помилка <class ‘ValueError’> : invalid literal for

    int() with base 10: ‘33.4’

Якщо користувач введе 0, то отримає повідомлення:

Помилка <class ‘ZeroDivisionError’> : division by

    zero

 

 

Оператор raise дає змогу програмісту згенерувати вказаний виняток. В єдиному аргументі raise вказується виняток, який буде викликано, наприклад:

>>> raise ZeroDivisionError(‘Ділення на нуль.’)

Traceback (most recent call last):

   File «<pyshell#9>«, line 1, in <module>

   raise ZeroDivisionError(‘Ділення на нуль’)

ZeroDivisionError: Ділення на нуль

 

Щодо класів винятків найбільший інтерес із практичного погляду становлять такі:

v виняток IndexError: виникає, коли індекс (наприклад, для елементу списку) вказано неправильно (виходить за межі допустимого діапазону). Списки обговорюються в одному із наступних розділів.

v  виняток KeyError: виникає, коли неправильно вказано ключ словника. Словники обговорюються в одному із наступних розділів.

v  виняток NameError: виникає, якщо не вдається знайти локальне або глобальне ім’я (змінну) з певною назвою.

v  виняток SyntaxError: пов’язаний з наявністю синтаксичних помилок.

v  виняток TypeError: пов’язаний з несумісністю типів, коли для обробки (наприклад, у функції) потрібне значення певного типу, а передається значення іншого типу.

Деякі з цих винятків ми використовуватимемо (явно або неявно) у прикладах.

 

 

Ø Механізм обробки виняткових ситуацій базується на використанні конструкції try-except. Якщо під час виконання програмного коду, поміченого ключовим словом try, виникає помилка, її може бути перехоплено й оброблено в одному з блоків, позначених інструкцією except. Для кожного except-блоку після ключового слова except можна вказати тип помилки (винятку), яка оброблюється цим блоком

 

Питання для перевірки засвоєних знань

1. Що таке виняток в програмуванні?

2. Як винятки допомагають у програмах?

3. Які ключові слова використовують для обробки винятків у Python?

4. Як використовувати блок try-except для обробки винятків?

5. Які типи винятків ви знаєте в Python?

6. Які основні принципи обробки винятків?

7. Які додаткові можливості забезпечують блоки else та finally у конструкції try-except?

8. Що таке «стек викликів» у зв’язку з винятками?

9. Як ви визначаєте та генеруєте власні винятки?

10. Чому важливо правильно обробляти винятки у програмах?

Попередня тема

На початок

Наступна тема