Python · Синтаксис · Начальный уровень
Основы обработки ошибок
Научитесь обрабатывать ошибки с помощью try/except/else/finally, генерировать собственные исключения и писать надёжный Python-код.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
Паттерны обработки ошибок
#Справочник: обработка ошибок
#Упражнения:
Безопасное преобразование в 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
Безопасное деление
#Напишите функцию `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
Несколько типов исключений
#Напишите функцию `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
Валидация возраста
#Напишите функцию `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)
Использование 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
Очистка в 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"))
Безопасный доступ к вложенному словарю
#Напишите функцию `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
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)
Цепочка операций
#Напишите функцию `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
Отчёт об исключениях
#Напишите функцию `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"]