Урок 11 – Обробка Помилок

Вступ

Якщо ви їх ще не зустрічали, тоді ви просто ще недостатньо старалися! Хто вони? Помилки. Проблеми. Баги. Винятки. Жуки. Розумієте про що я? Я зрозумів про що це з оцієї програми:

Це приклад програми меню, яку ми, якщо пам’ятаєте, писали в одному із попередніх уроків. На перший погляд програма виглядає просто чудово! Принаймні до моменту, допоки ми з вами пробуємо її вперше запустити. Ану давайте запустіть її. Що отримали?

Жуки (Bugs, Баги) – Людські Помилки

Найбільш часті проблеми з вашим кодом будуть ті, які ви самі ж зробите 😉 Сумно, але правда. Отже, запустивши вище наведений приклад коду ми з вами отримаємо наступний вивід:

Знаєте що? Python намагається сказати в чому причина поломки нашої програми. Він дає нам знати, що ми не можемо віднімати число від стрічки. Давайте ще раз глянемо на помилку (трейсбек, traceback – так називається повний вивід помилки):

  • “File “test.py”, line 14, in <module>” – пояснює нам кілька речей: в якому файлі (модулі) виникла помилка, та у якій саме стрічці (рядочку) цього файла. Ця інформація є дуже корисною, особливо якщо ми працюємо одночасно над великою кількістю модулів та Пітон коду. Слово <module> каже нам, що помилка сталася на кореневому рівні нашого модуля (а не в всередині функції чи класу)
  • “answer = menu(options, ‘Яка ваша улюблена літера? ‘)” – далі бачимо цю стрічку, яка показує якраз стрічку номер 14 у нашому файлі. У даній стрічці бачимо виклик нашої функції menu, саме тому наступна стрічка трейсбеку заходить в дану функцію і ще більше деталізує нам причину поломки:
  • “File “test.py”, line 8, in menu” – всередині функції ‘menu’ в рядочку 8 нашого модуля маємо наступний код:
  • “return raw_input(question) – 1” – який і є причиною нашої помилки. Очевидно функція raw_input повертає дані типу стрічка, і не конвертує введений користувачем номер меню в число, а передає його як стрічку. Тому операція віднімання одинички від стрічки закінчується поломкою:
  • “TypeError: unsupported operand type(s) for -: ‘str’ and ‘int'” – останній рядочок трейсбеку є найбільш важливим і розказує нам конкретно про тип помилки та дає нам її деталі. У нашому випадку ми маємо ‘TypeError’, що означає нестиковку різних типів даних – стрічки та числа. Тобто Пітон не може відняти об’єкт типу типу ‘int’ від об’єкта типу ‘str’.

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

Ось приклад такого складнішого трейсбеку (це моя помилка при розробці під платформу Plone CMS):

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

Значить вертаємося до нашої попередньої помилки. Ми вже знаємо її причину, так? Адже на рядочку 8-му нашої програми ми пробуємо відняти число від стрічки:

Функція ‘raw_input’ завжди повертає стрічку, тому у нас є проблемка. Давайте змінимо її на функцію ‘input’. Дана функція не лише отримує та повертає у нашу програму дані введені користувачем, але вона ще й пробує вгадати та конвертувати їх до типу даних існуючих в Пітоні. Тобто, якщо користувач ввів число ‘5’, тоді вона поверне в програму нам не стрічку ‘5’, а число 5.

Після зміненого рядочка наша програма виглядатиме наступним чином:

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

Отже, помилку вирішено!

Винятки (Exceptions, Ексепшини) – Обмеження Коду

Гаразд. Знаємо, що програма зазвичай працює добре, якщо ми не заставляємо її робити якихось дивних речей 🙂 Але що, якщо спробувати? Наприклад, запускаємо нашу програму і “випадково” передаємо в меню програми не число, а літеру. Упс!

У прикладі вище я передав нашій програмі літеру ‘l’, і отримав таку помилку (трейсбек). Що вона нам каже? В трейсбеку можемо бачити наведено два рядочки коду. Один в рядочку 8 і інший в рядочку 14.

Давайте спробуємо з’ясувати, в чому проблема. Маємо NameError, який каже нам, що змінна під назвою ‘l’ не визначена. Якщо ще пам’ятаєте, перед отриманням даної помилки я ввів в меню літеру ‘l’. Зауважили зв’язок? 🙂

Справа в тому, що функція ‘input’ при конвертації, отримавши символ чи стрічку, пробує трактувати дану стрічку як назву змінної у нашій програмі. Зрозуміло, що ми не визначали ніякої змінної під назвою ‘l’, тому отримуємо помилку типу ‘NameError’. Тобто, якщо користувач введе у меню нашої програми що-небудь окрім чисел від 1 до 8 – він зазвичай отримає помилку.

Це погано! Що ж будемо з цим робити? Одним з найкращих та простих варіантів обробки таких ситуацій є використання пари операторів try та except. Давайте одразу до швидкого прикладу, а потім вже розберемо його:

Це є приклад використання пари операторів ‘try’, ‘except’, щоб відловити та обробити помилку, при цьому не показавши її на зовні користувачеві. Ми запускаємо наш код, той який може зламатися за певних умов, всередині оператора ‘try:’. Другою гілкою буде ‘except:’, її тілом є код, який запуститься у випадку, якщо тіло оператора ‘try:’ таки видасть програмну помилку. В протилежному випадку код всередині оператора ‘except:’ запущений не буде, і наша програма піде собі далі по коду.

Тобто, try-except пара працює подібно до if-else, але не має аргументом логічну умову (логічний вираз), а слідкує чи не поламається код всередині тіла оператора ‘try:’.

Тепер давайте спробуємо використати нашу пару операторів ‘try’ – ‘except’ у нашій попередньо визначеній функції ‘menu’, таким чином, щоб замість отримання поломки, ми б отримували гарне повідомлення на екран про неправильний ввід користувача:

Зверніть увагу. Ми скористалися оператором except у вигляді:

Це означає, що ми не ловимо усі помилки, а лише помилки з типом NameError. Без такого аргументу наш оператор ‘except’ зловить для нас усі-усі помилки, що трапляться в тілі оператора ‘try’.

А ну тепер спробуйте запустити нашу оновлену програму і замість числа 1-8 введіть будь-яку літеру. Що бачите? Саме так! Спробували пофіксити одну помилку, а отримали іншу. Але тепер бачимо, що вона сталася вже далі по коду. Тобто поперднє місце поломки з допомогою операторі try/except нам вдалося пройти. Так часто буває 😉

Отже, давайте глянемо детальніше на нову помилку:

Які ідеї, чому цього разу трапилася помилка?

Проблема в тому, що наша програма очікує від функції ‘menu’ саме числа, яке потім ми з вами використовуємо як індекс для пошуку серед опцій: ‘optionі[answer]’. Що ж змінилося? А те, що у випадку попередньої помилки ми її відловлюємо та натомість того щоб що-небудь повернути з функції, ми просто друкуємо повідомлення на екран. А у мові Пітон, якщо нічого не повертати явно з функції (не використати оператор ‘return’), тоді функція автоматично повертає ‘None’ – порожнє значення. Ну і зрозуміло, що спроба отримати елемент зі списку ‘options’ під індексом ‘None’ (options[None]) закінчується невдачею. Адже в Пітоні елементи списку пронумеровані числами починаючи з нуля і ніякого None там немає 😉

Що ж робити далі? Є як мінімум два варіанти:

  1. скористатися операторами умови ‘if’/’else’, та перевіряти чи ми отримали None замість числа з функції ‘menu’. Якщо так – не пробуємо отримати опцію з меню, а друкуємо ще одне повідомлення з програми
  2. скористатися операторами ‘try’/’except’, щоб зловити тепер нашу другу помилку та знову ж таки вивести повідомлення на екран у випадку поломки

Оберемо другий варіант для закріплення знань по оператору ‘try’. Ось як виглядатиме наша програма з використаними операторами ‘try’/’except’ вже вдруге:

Вуаля! Все працює, проблему вирішено!

Тепер ви майже гуру у вирішенні проблем при програмування мовою Python. А це дуже важливо, адже програміст – це впершу чергу людина, яка вміє вирішувати виникаючи проблеми, а не просто знає мову програмування.

Happy End

Є питання по уроку? Прошу задавати в коментарях – будемо розбиратись!

На цьому все! Це наш останній урок з вивчення мови програмування Python версія 2! Урааа! Вітаю з успішним проходженням, так тримати! Надіюсь було корисно!

Що далі? А далі або рухаємось до поглиблених спеціалізованих курсів та матеріалів по мові Пітон, якими інтернет прямо таки ‘кишить’. Або, якщо плануєте програмувати під веб, тоді раджу тепер переходити до освоєння азів мови веб розмітки HTML!

Коментарі:

  1. Цікавий урок, але скопіювавши з сайту, вставивши і запустивши програму (та яка мала би бути з помилкою) в IDEшку (PyCharm) я отримав:
    —————————————————-
    1 ) A
    2 ) B
    3 ) C
    4 ) D
    5 ) E
    6 ) F
    7 ) H
    8 ) I
    Яка ваша улюблена літера?
    ————————–
    Це ж нормальна поведінка програми чи не так?

    Відповісти
  2. З відступами якось не гарно вийшло… Але як вставляєш вони є, а при збереженні зникають…

    Відповісти
  3. Дуже цікава вийшло… Під час виправлень Вашого прикладу (тобто першого використання try, except) я випадково залишив стрічку
    return input(question) – 1 яка опинилась після оператора except.
    Тобто програма виглядала так:
    # -*- coding: utf-8 -*-
    def menu(list, question):
    for entry in list:
    print 1 + list.index(entry),
    print “) ” + entry
    try:
    return input(question) – 1
    except NameError:
    print u’Будь-ласка, ввудіть число від 1 до 8′

    return input(question) – 1

    options = [‘A’,’B’,’C’,’D’,’E’,’F’,’H’,’I’]

    answer = menu(options, u’Яка ваша улюблена літера? ‘)

    print u’Ваша відповідь ‘ + (options[answer])
    І у мене все працювало – тобто не було цієї другої помилки! функція нормально повертала потрібне значення. Перевірив кілька разів поки помітив цю стрічку.
    Правда виникло питання – Якщо ввести число більше від 8 – і ось тут помогло друге використання try except
    До речі – коли спробував Ваш варіант в мене чомусь якщо вводити правильну відповідь після першого except програма повертала не літеру, а число яке я вводив…

    Відповісти
    • “став pdb і дебаж”
      Ва-а-ау! Після Вашої відповіді відчув себе майже програмістом.
      Що таке дебаж – Google мені підказав, pdb – можливо формат файлу…
      Вибачте, Віталій – я все-таки лиш 10 днів назад ввів свій перший в житті код. Тай у моєму пості йшлося не про помилки. Навпаки програма нормально працювала і після першого використання try, except
      завдяки випадково залишеній стрічці return input(question) – 1. Я просто хотів показати, що так теж можна вирішити проблему.

  4. На жаль всі відступи посипались (
    До речі, а як їх зберегти при копіюванні у віконце коменту?

    Відповісти
  5. # Ще такий варіант )
    # -*- coding: utf-8 -*-
    def ok_check (choice):# перевірка діапазону індекса
    ok = False
    k=0
    for it in options:
    k+=1
    ok= ok or (int(choice)==k)
    return ok
    def menu(list, question):
    opt=0
    for j in list:
    print 1 + list.index(j),
    print “) “, j
    ou=1
    while not ok_check (opt):
    try:
    opt=input(question)
    except:
    print u”Натуральні числа від 1 до 9, будь ласка!”
    return -1
    if not ok_check (opt):
    print u”Введіть коректний номер опції!”
    if ou==3:# три спроби ввести криве число
    print u”!? Постійно – не в діапазоні..”
    return -1
    else:
    ou+=1
    return int(opt)-1
    #–module
    options = [‘A’,’B’,’C’,’D’,’E’,’F’,’G’,’H’,’I’]
    print u”Є такі варіанти вибору:”
    ans=menu(options, u’Яка ваша улюблена літера? №: ‘)
    if ok_check(ans+1):
    print u”Ваша відповідь: ” , options[ans]
    else:
    print u”Випийте чайку, зосередьтесь!)”
    #–endom

    Відповісти
  6. Підкажіть, будь ласка, в чому може бути проблема

    Відповісти
  7. Привіт! Намагаюсь віддекодити стрічку, котра може бути ASCII, або cp1251.
    Для цього скористався вашим уроком. Проте, всеодно вилітає бага
    File “/home/uu/tinytag.py”, line 212, in
    asciidecode = lambda x: self._unpad(codecs.decode(x, ‘ASCII’))
    UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xc5 in position 0: ordinal not in range(128)

    Ось мій код:
    try:
    asciidecode = lambda x: self._unpad(codecs.decode(x, ‘ASCII’))
    except UnicodeDecodeError:
    print “Something wrong!!”
    asciidecode = lambda x: self._unpad(codecs.decode(x, ‘cp1251’))

    Відповісти

Опублікувати коментар

Ваша e-mail адреса не оприлюднюватиметься.

Ви не робот? ;) *