Python · Синтаксис · Початковий рівень

Основи обробки помилок

10 завдань

Навчіться обробляти помилки за допомогою try/except/else/finally, викидати власні виключення та писати надійний Python-код.

try / except / else / finally

#
Помилки у Python називаються **виключеннями** (exceptions). Замість того, щоб програма падала, можна **обробляти** виключення за допомогою блоку `try`. ## Базовий try/except ```python try: number = int("abc") # кидає ValueError except ValueError: print("Це не число!") # Програма продовжує роботу нормально print("Після блоку try.") ``` Без `try/except` програма зупинилась би на `int("abc")`. З ним ми перехоплюємо помилку і продовжуємо. ## Перехоплення кількох типів виключень ```python try: value = int(user_input) result = 10 / value except ValueError: print("Будь ласка, введіть число.") except ZeroDivisionError: print("Число не може бути нулем.") ``` Можна перехопити кілька одразу: ```python except (ValueError, TypeError): print("Невірний тип або значення введення.") ``` ## Гілка else Блок `else` виконується тільки якщо **виключення не виникло** у `try`: ```python try: result = int("42") except ValueError: print("Перетворення не вдалось.") else: print(f"Успіх! Результат: {result}") # виконується тільки якщо try пройшов без помилок ``` Використовуйте `else` для коду, який має виконуватись тільки при успіху — це тримає логіку щасливого шляху окремо від обробки помилок. ## Гілка finally `finally` виконується **завжди** — незалежно від того, чи виникло виключення: ```python try: f = open("data.txt") data = f.read() except FileNotFoundError: print("Файл не знайдено.") finally: f.close() # завжди закриваємо файл ``` `finally` використовується для прибирання: закриття файлів, з'єднань, звільнення ресурсів. ## Доступ до об'єкту виключення ```python try: result = 10 / 0 except ZeroDivisionError as e: print(f"Помилка: {e}") # Помилка: division by zero print(type(e)) # <class 'ZeroDivisionError'> ``` ## raise: виклик виключень ```python def set_age(age): if age < 0: raise ValueError(f"Вік не може бути від'ємним: {age}") if age > 150: raise ValueError(f"Вік {age} нереально великий.") return age try: set_age(-5) except ValueError as e: print(e) # Вік не може бути від'ємним: -5 ``` `raise` дозволяє сигналізувати про помилки у власному коді з зрозумілим повідомленням. ## Повторний виклик виключення ```python try: do_something() except ValueError as e: print(f"Логуємо помилку: {e}") raise # повторно кидає те ж виключення, не замовчуємо його ```

Патерни обробки помилок

#
## Патерн 1: безпечне перетворення типів Безпечно перетворіть введення користувача і повертайте розумне значення за замовчуванням при помилці: ```python def safe_int(s, default=0): try: return int(s) except (ValueError, TypeError): return default print(safe_int("42")) # 42 print(safe_int("abc")) # 0 print(safe_int(None)) # 0 print(safe_int("7", -1)) # 7 ``` ## Патерн 2: валідація з raise Перевіряйте на межі системи; кидайте виключення якщо дані невалідні: ```python def validate_email(email): if not isinstance(email, str): raise TypeError(f"email має бути рядком, отримано {type(email).__name__}") if "@" not in email: raise ValueError(f"Невалідний email: {email!r}") return email.strip().lower() try: email = validate_email(" [email protected] ") print(email) # [email protected] except (TypeError, ValueError) as e: print(f"Помилка валідації: {e}") ``` ## Патерн 3: кілька операцій, один обробник Групуйте пов'язані ризиковані операції разом: ```python def load_config(path): try: with open(path) as f: import json data = json.load(f) return data except FileNotFoundError: print(f"Файл конфігурації не знайдено: {path}") return {} except json.JSONDecodeError as e: print(f"Невалідний JSON у {path}: {e}") return {} ``` ## Патерн 4: else для логіки успіху ```python def divide(a, b): try: result = a / b except ZeroDivisionError: print("Ділення на нуль неможливе.") return None else: # Виконується тільки якщо виключення не виникло print(f"{a} / {b} = {result:.4f}") return result divide(10, 3) # 10 / 3 = 3.3333 divide(10, 0) # Ділення на нуль неможливе. ``` ## Патерн 5: finally для гарантованого очищення ```python def read_first_line(path): f = None try: f = open(path) return f.readline().strip() except FileNotFoundError: return None finally: if f: f.close() # виконується навіть якщо виникло виключення ``` У сучасному Python оператор `with` виконує очищення автоматично: ```python def read_first_line(path): try: with open(path) as f: return f.readline().strip() except FileNotFoundError: return None ```

Довідник: обробка помилок

#
## Повний синтаксис ```python try: # код, що може кинути виключення except ExceptionType: # виконується якщо виникло ExceptionType (або підклас) except (TypeError, ValueError) as e: # перехоплює будь-який з типів; e містить об'єкт виключення except Exception: # перехоплює будь-яке виключення (використовуйте обережно) else: # виконується ТІЛЬКИ якщо у try не виникло виключення finally: # виконується ЗАВЖДИ, незалежно від виключення ``` ## Поширені вбудовані виключення | Виключення | Коли виникає | |---|---| | `ValueError` | Правильний тип, хибне значення (`int("abc")`) | | `TypeError` | Хибний тип (`"a" + 1`) | | `KeyError` | Ключ словника не знайдено (`d["missing"]`) | | `IndexError` | Індекс списку поза межами (`lst[99]`) | | `AttributeError` | У об'єкта немає такого атрибуту (`None.upper()`) | | `ZeroDivisionError` | Ділення на нуль (`1 / 0`) | | `FileNotFoundError` | Файл не існує | | `PermissionError` | Немає прав доступу до файлу | | `ImportError` | Модуль не знайдено | | `StopIteration` | Ітератор вичерпано | | `OverflowError` | Числовий результат занадто великий | | `MemoryError` | Недостатньо пам'яті | | `RecursionError` | Перевищено глибину рекурсії | | `AssertionError` | Оператор `assert` не виконався | | `NotImplementedError` | Абстрактний метод не реалізовано | | `Exception` | Базовий клас усіх не-системних виключень | ## Форми raise ```python raise ValueError("повідомлення") # кинути нове виключення raise ValueError("msg") from original # ланцюжок виключень raise # повторно кинути поточне ``` ## Ієрархія виключень (спрощено) ``` BaseException └── Exception ├── ValueError ├── TypeError ├── LookupError │ ├── KeyError │ └── IndexError ├── ArithmeticError │ └── ZeroDivisionError ├── OSError │ ├── FileNotFoundError │ └── PermissionError └── ... ``` ## Ключові правила ```python # Завжди перехоплюйте конкретні виключення, не голий `except:` # ПОГАНО: try: ... except: # перехоплює KeyboardInterrupt, SystemExit тощо! pass # ДОБРЕ: try: ... except ValueError: handle() # Використовуйте else для коду "тільки при успіху" try: result = compute() except ComputeError: ... else: save(result) # тільки якщо compute() успішний # Використовуйте finally для гарантованого очищення try: acquire_resource() use_resource() finally: release_resource() # виконується завжди ```
01

Безпечне перетворення у int

#

Напишіть функцію `safe_int(s, default=0)`, яка намагається перетворити `s` на ціле число і повертає його при успіху, або `default` якщо перетворення неможливе. Ніколи не повинна кидати виключення. Приклад: `safe_int("42")` → `42`, `safe_int("abc")` → `0`, `safe_int("abc", -1)` → `-1`.

def safe_int(s, default=0):
    pass


print(safe_int("42"))        # 42
print(safe_int("abc"))       # 0
print(safe_int("abc", -1))  # -1
Рішення
def safe_int(s, default=0):
    try:
        return int(s)
    except (ValueError, TypeError):
        return default


print(safe_int("42"))        # 42
print(safe_int("abc"))       # 0
print(safe_int("abc", -1))  # -1
02

Безпечне ділення

#

Напишіть функцію `safe_divide(a, b)`, яка повертає `a / b` як float, або `None` якщо `b` дорівнює нулю. Використовуйте try/except. Приклад: `safe_divide(10, 2)` → `5.0`, `safe_divide(10, 0)` → `None`.

def safe_divide(a, b):
    pass


print(safe_divide(10, 2))   # 5.0
print(safe_divide(10, 0))   # None
Рішення
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None


print(safe_divide(10, 2))   # 5.0
print(safe_divide(10, 0))   # None
03

Кілька типів виключень

#

Напишіть функцію `parse_value(s)`, яка: - Повертає `int(s)` якщо `s` можна перетворити на ціле - Повертає `float(s)` якщо можна на float, але не на int - Повертає `None` якщо жодне перетворення не спрацьовує Не використовуйте `.` для визначення float — тільки try/except.

def parse_value(s):
    pass


print(parse_value("42"))     # 42   (int)
print(parse_value("3.14"))   # 3.14 (float)
print(parse_value("abc"))    # None
Рішення
def parse_value(s):
    try:
        return int(s)
    except ValueError:
        pass
    try:
        return float(s)
    except ValueError:
        return None


print(parse_value("42"))     # 42
print(parse_value("3.14"))   # 3.14
print(parse_value("abc"))    # None
04

Валідація віку

#

Напишіть функцію `validate_age(age)`, яка: - Кидає `TypeError` якщо `age` не є цілим числом - Кидає `ValueError` з повідомленням `"Age must be between 0 and 150"` якщо вік поза цим діапазоном - Повертає `age` якщо валідний Потім напишіть код, що викликає `validate_age` з введенням користувача і обробляє обидва виключення з відповідними повідомленнями.

def validate_age(age):
    pass


print(validate_age(25))   # 25
try:
    validate_age("old")
except TypeError as e:
    print(e)
try:
    validate_age(200)
except ValueError as e:
    print(e)
Рішення
def validate_age(age):
    if not isinstance(age, int):
        raise TypeError(f"Expected int, got {type(age).__name__}")
    if not 0 <= age <= 150:
        raise ValueError("Age must be between 0 and 150")
    return age


print(validate_age(25))   # 25
try:
    validate_age("old")
except TypeError as e:
    print(e)
try:
    validate_age(200)
except ValueError as e:
    print(e)
05

Використання else

#

Напишіть функцію `read_number(s)`, яка: - Намагається перетворити `s` у float - Якщо не вдається — виводить `"Invalid input"` і повертає `None` - Якщо вдається (використайте `else`) — виводить `"Got: {value:.2f}"` і повертає float Виведення у випадку успіху має бути всередині блоку `else`, а не всередині `try`.

def read_number(s):
    pass


print(read_number("3.14"))   # виводить "Got: 3.14", повертає 3.14
print(read_number("abc"))    # виводить "Invalid input", повертає None
Рішення
def read_number(s):
    try:
        value = float(s)
    except ValueError:
        print("Invalid input")
        return None
    else:
        print(f"Got: {value:.2f}")
        return value


print(read_number("3.14"))   # Got: 3.14
print(read_number("abc"))    # Invalid input
06

Очищення у finally

#

Напишіть функцію `safe_open(path)`, яка: - Відкриває файл за `path` і читає його вміст - Повертає вміст як рядок при успіху - Повертає порожній рядок якщо виникає `FileNotFoundError` - Завжди виводить `"Closing file"` у блоці `finally`, незалежно від того чи вдалось відкрити файл (Можна симулювати відкриття через змінну яка може бути None.)

def safe_open(path):
    pass


print(safe_open("/tmp/test_eh.txt"))      # вміст або ""
print(safe_open("/tmp/no_such_file.txt")) # ""
Рішення
def safe_open(path):
    f = None
    try:
        f = open(path, encoding="utf-8")
        return f.read()
    except FileNotFoundError:
        return ""
    finally:
        print("Closing file")
        if f:
            f.close()


import pathlib
pathlib.Path("/tmp/test_eh.txt").write_text("hello", encoding="utf-8")
print(safe_open("/tmp/test_eh.txt"))
print(safe_open("/tmp/no_such_file.txt"))
07

Безпечний доступ до вкладеного словника

#

Напишіть функцію `get_user_city(data, username)`, яка отримує `data[username]["address"]["city"]`. Обробіть `KeyError` якщо будь-який ключ відсутній і повертайте `"Unknown"` у такому випадку. Приклад: `get_user_city({"alice": {"address": {"city": "Kyiv"}}}, "alice")` → `"Kyiv"`, `get_user_city({}, "bob")` → `"Unknown"`.

def get_user_city(data, username):
    pass


users = {"alice": {"address": {"city": "Kyiv"}}}
print(get_user_city(users, "alice"))   # Kyiv
print(get_user_city(users, "bob"))     # Unknown
Рішення
def get_user_city(data, username):
    try:
        return data[username]["address"]["city"]
    except KeyError:
        return "Unknown"


users = {"alice": {"address": {"city": "Kyiv"}}}
print(get_user_city(users, "alice"))   # Kyiv
print(get_user_city(users, "bob"))     # Unknown
08

Raise з валідацією

#

Напишіть функцію `create_username(name)`, яка: - Кидає `ValueError` якщо `name` порожній або містить тільки пробіли - Кидає `ValueError` якщо `name` довший за 20 символів - Кидає `ValueError` якщо `name` містить пробіли - Інакше повертає `name.lower().strip()` Додайте зрозуміле повідомлення про помилку в кожен `ValueError`.

def create_username(name):
    pass


print(create_username("Alice"))         # alice
print(create_username("  Bob  "))       # bob
try:
    create_username("")
except ValueError as e:
    print(e)
Рішення
def create_username(name):
    name = name.strip()
    if not name:
        raise ValueError("Username cannot be empty")
    if len(name) > 20:
        raise ValueError("Username must be 20 characters or less")
    if " " in name:
        raise ValueError("Username cannot contain spaces")
    return name.lower()


print(create_username("Alice"))         # alice
print(create_username("  Bob  "))       # bob
try:
    create_username("")
except ValueError as e:
    print(e)
09

Ланцюжок операцій

#

Напишіть функцію `process(text)`, яка виконує три операції послідовно: 1. Перетворює `text` у верхній регістр 2. Розбиває за комою і бере перший елемент 3. Перетворює перший елемент у ціле число Обробіть `ValueError`, `IndexError` та `AttributeError` в одній гілці `except` і повертайте `None` при будь-якій помилці. При успіху повертайте ціле число.

def process(text):
    pass


print(process("42,extra"))   # 42
print(process("no-comma"))   # None
print(process(None))         # None
Рішення
def process(text):
    try:
        upper = text.upper()
        first = upper.split(",")[0]
        return int(first)
    except (ValueError, IndexError, AttributeError):
        return None


print(process("42,extra"))   # 42
print(process("no-comma"))   # None
print(process(None))         # None
10

Звіт про виключення

#

Напишіть функцію `run_all(functions, input_value)`, яка приймає список функцій і запускає кожну з `input_value`. Повертає список результатів, де кожен елемент або повернене значення (якщо функція успішна), або рядок `"ERROR: {exception_message}"` якщо виникло виключення. Приклад: з `functions = [str, int, lambda x: x/0]` і `input_value = "42"`, результат: `["42", 42, "ERROR: division by zero"]`.

def run_all(functions, input_value):
    pass


funcs = [str, int, lambda x: x / 0]
print(run_all(funcs, "42"))
# ["42", 42, "ERROR: division by zero"]
Рішення
def run_all(functions, input_value):
    results = []
    for fn in functions:
        try:
            results.append(fn(input_value))
        except Exception as e:
            results.append(f"ERROR: {e}")
    return results


funcs = [str, int, lambda x: x / 0]
print(run_all(funcs, "42"))
# ["42", 42, "ERROR: division by zero"]