Українська
Власні винятки та їхні групи
Власні винятки та межі відповідальності
Для виразної назви проблеми достатньо мінімального класу: class InsufficientFundsError(Exception): та вкладеного pass. Exception у дужках – базовий клас; pass означає відсутність додаткових дій. Повний механізм класів буде розглянуто в темі 8. Тут нас цікавить лише окремий тип, який можна підняти й перехопити.
Власну ієрархію будують за діями обробника. Якщо брак коштів і закритий рахунок однаково завершують переказ, їх можна об’єднати базовим PaymentError. Проте неправильний пароль не слід називати ValueError тільки тому, що це також «неправильне значення»: предметне ім’я пояснює, яка операція не відбулася.
Приклад 2. Навчальний банківський рахунок
Залишок і сума списання подаються цілими копійками. Функція не змінює залишок до завершення всіх перевірок і повертає новий. Нечисловий запис перетворюється на PaymentError зі збереженою причиною; нестача коштів має окремий тип. Це арифметична модель, а не реалізація банківської системи.
py
import logging
logging.basicConfig(level=logging.INFO,
format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)
class PaymentError(Exception):
pass
class InsufficientFundsError(PaymentError):
pass
def debit(balance: int, text: str) -> int:
try:
amount = int(text)
except ValueError as error:
raise PaymentError("сума має бути цілою") from error
if amount <= 0:
raise PaymentError("сума має бути додатною")
if amount > balance:
raise InsufficientFundsError("недостатньо коштів")
return balance - amount
balance = 5000
for text in ("1200", "4000", "abc"):
try:
balance = debit(balance, text)
except InsufficientFundsError as error:
logger.warning("Відмовлено: %s", error)
except PaymentError as error:
logger.error("Відмовлено: %s", error)
else:
logger.info("Залишок: %s коп.", balance)INFO: Залишок: 3800 коп.
WARNING: Відмовлено: недостатньо коштів
ERROR: Відмовлено: сума має бути цілоюПісля обох відмов залишок дорівнює 3800. Присвоєння ліворуч від виклику відбувається лише після його успішного повернення. Для abc причина має тип ValueError, хоча кінцевий обробник бачить PaymentError. Щоб одержати весь ланцюг у журналі розробника, замініть останній logger.error на logger.exception усередині обробника. Не журналюйте паролі та платіжні реквізити.
Групи винятків і примітки
Звичайний raise зупиняє перевірку на першій помилці. Для анкети з кількома незалежними полями зручніше показати всі проблеми одразу. ExceptionGroup об’єднує непорожню послідовність винятків; except* відбирає підгрупи за типом. На відміну від звичайного except, кілька обробників except* можуть виконатися для однієї групи. Необроблена частина передається вище.
Тут використаємо список: [] створює порожній змінний набір елементів, append додає один елемент, len рахує їх. Перевірка if errors істинна для непорожнього списку. Класи колекцій розглядатимуться детальніше пізніше; включення та складні перетворення для цього прикладу не потрібні.
Приклад 3. Анкета учасника
Навчальна анкета містить ім’я й вік від 16 до 100. Порожнє ім’я, нечисловий вік та вихід за межі – незалежні правила. Метод add_note додає рядкову примітку до помилки; стандартний traceback показує її після повідомлення. Примітка не змінює тип винятку.
py
def validate(name: str, age_text: str) -> None:
errors: list[Exception] = []
if not name.strip():
error = ValueError("ім’я порожнє")
error.add_note("Поле: name")
errors.append(error)
try:
age = int(age_text)
if not 16 <= age <= 100:
raise ValueError("вік поза межами 16..100")
except ValueError as error:
error.add_note("Поле: age")
errors.append(error)
if errors:
raise ExceptionGroup("Анкету не прийнято", errors)
try:
validate(" ", "12")
except* ValueError as group:
print("Помилок:", len(group.exceptions))
for error in group.exceptions:
print(error)
for note in error.__notes__:
print(note)Помилок: 2
ім’я порожнє
Поле: name
вік поза межами 16..100
Поле: ageУ цьому прикладі група пласка, усі її елементи мають примітки, тому прямий доступ до __notes__ виправданий. Загалом групи можуть бути вкладеними, а довільний виняток може не мати цього атрибута. Для універсального обходу потрібна перевірка структури, якої тут не вимагаємо. validate("Олена", "20") завершується без винятку.
У межах однієї конструкції не можна змішувати except і except*. Безпосередньо в тілі except* не дозволені переходи return, break, continue. Група не запускає паралельне виконання: у прикладі перевірки виконуються послідовно. Офіційний опис: https://docs.python.org/3.14/tutorial/errors.html#raising-and-handling-multiple-unrelated-exceptions.