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"]