---
tags: Python Advanced
---
# Практика 9. Помилки та винятки. Обробка винятків
## Базовий рівень. Просте налагодження програм
Для кожного завдання:
* використовуючи візуальний перегляд коду або відладчик в IDE:
* розберіться в алгоритмі розв'язання;
* знайдіть номери рядків, у яких є помилки, та вкажіть їх у рядку документації;
* виправте помилки, мінімально змінивши код (зазвичай, замінивши рядок на рядок); помилки виправляються за ходом програми, зверху вниз.
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл): !!!
"""
def even_mult(nums):
"""Повернути добуток парних чисел у списку 'nums'."""
p = 0
for item in nums:
if item % 2 != 0:
p *= item
return p
```
Виправлена програма:
```python=
"""
Помилки (номери рядків через пробіл): 8 10
"""
def even_mult(nums):
"""Повернути добуток парних чисел у списку 'nums'."""
p = 1
for item in nums:
if item % 2 == 0:
p *= item
return p
```
### Задача 1
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): !!!
"""
def sum_of_digits(n):
"""Повернути суму цифр менших 5 для позитивного цілого числа `n`.
Якщо таких цифр немає, повернути 0."""
с = 0
while n > 0:
digit = n // 10
if digit < 5:
c = с + 1
n //= 10
return digit
```
:::spoiler Виправлена програма:

<!--
-->
:::
### Задача 2
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): !!!
"""
def primes(a, b):
"""Повернути список простих чисел на відрізку від 'a' до 'b'."""
res = []
c = 0
for i in range(a, b):
for j in range(i):
if i % (j + 1) == 0:
c += 1
if c == 2:
res.append(i)
return res
```
:::spoiler Виправлена програма:

<!--
-->
:::
### Задача 3
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): !!!
"""
def first_vacant_row(seats):
"""Повернути перший ряд, в якому є найбільше
вільних місць та їх кількість.
Повертається нумерація рядів із 1. Якщо вільних місць немає, повернути 0, 0.
Параметры:
- seats (list of list): інформація про продані квитки
(1 - продано, 0 - ні).
Результат:
- tuple (ряд, кількість місць).
"""
max_count = 0
max_row = 0
for row_index, row in enumerate(seats):
available_seats_count = row.count(0) # 0 - пусто
if available_seats_count >= max_count:
max_row = row_index
max_count = available_seats_count
return max_row, max_count
```
:::spoiler Виправлена програма:

<!--
-->
:::
### Задача 4
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): !!!
"""
def min_pair(nums):
"""Повернути мінімальну суму сусідніх 2-х чисел у списку 'nums'."""
min = nums[0] * nums[1]
for i in range(2, len(nums)):
min = min(nums[i] + nums[i + 1], min)
return min
# import random
#
# random.seed(50)
#
# N_MAX = 10
# RANGE_MIN = 1
# RANGE_MAX = 100
# nums = random.sample(range(RANGE_MIN, RANGE_MAX), N_MAX)
#
# print(nums)
#
# print(min_pair(nums))
```
:::spoiler Виправлена програма:

<!--
-->
:::
### Задача 5
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): !!!
"""
def non_negatives(nums):
"""Удалить из списка чисел 'nums' отрицательные элементы и вернуть
измененный список."""
for i in range(len(nums)):
if nums[i] < 0:
del nums[i]
return nums
# import random
#
# n = 10
# nums = [round(random.uniform(-10, 10), 2) for i in range(n)]
# print(nums)
#
# non_negatives(nums)
# print(nums)
```
:::spoiler Виправлена програма:

<!--
-->
:::
## Середній рівень. Обробка винятків та cтвердження
Задачі 6-10 написані правильно, але містять місця потенційних помилок.
Для кожного завдання:
* знайдіть потенційні джерела помилок (вкажіть номери рядків у рядку документації);
* використовуючи конструкцію `try` додайте в код [обробку відповідних винятків](https://hackmd.io/@YaRo/python-exceptions#/3).
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл): !!!
"""
def avg(a, b):
"""Повернути середнє геометричне чисел 'a' та 'b'.
Параметри:
- a, b (int або float).
Результат:
- float.
"""
return (a * b) ** 0.5
a = float(input("a = "))
b = float(input("b = "))
c = avg(a, b)
print(f"Середнє геометричне = {c:.2f}")
```
:::spoiler Виправлена програма:
```python=
"""
Помилки (номери рядків через пробіл): 15 18 19
"""
def avg(a, b):
"""Повернути середнє геометричне чисел 'a' та 'b'.
Параметри:
- a, b (int або float).
Результат:
- float.
Винятки:
- ValueError: обчислення неможливе.
"""
if a * b >= 0:
return (a * b) ** 0.5
else:
raise ValueError("Неможливо визначити середнє геометричне "
"введених чисел.")
try:
a = float(input("a = "))
b = float(input("b = "))
c = avg(a, b)
print(f"Середнє геометричне = {c:.2f}")
except ValueError as exc:
print("Помилка:", exc, ". Перевірте введені числа.")
except Exception as exc:
print("Помилка:", exc)
```
<!--
-->
:::
### Задача 6
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): !!!
"""
def unemployment_rate(unemployed, employed):
"""Повернути рівень безробіття (РБ) як частку від 1.
Розрахунок за формулою: РБ = безробітні / (зайняті + безробітні).
"""
return unemployed / (unemployed + employed)
unemployed = int(input("Введіть кількість безробітних (людей): "))
employed = int(input("Введіть кількість зайнятих (людей): "))
rate = unemployment_rate(unemployed, employed)
print(f"Рівень безробіття = {rate:.1%}")
```
:::spoiler Підказка
1. **Аналіз потенційних помилок**:
* Користувач може ввести нечислові дані, що викличе `ValueError` при перетворенні `int(input(...))`.
* Якщо сума безробітних та зайнятих буде нуль, у функції `unemployment_rate` відбудеться ділення на нуль (`ZeroDivisionError`).
2. **Обробка помилок**:
* Оберніть блок введення даних та виклику функції у `try...except`.
* Створіть обробники для `ValueError` (неправильне введення) та `ZeroDivisionError` (ділення на нуль).
* Додайте загальний `except Exception` для інших непередбачених помилок.
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): 11 14 15
"""
def unemployment_rate(unemployed, employed):
"""Повернути рівень безробіття (РБ) як частку від 1.
Розрахунок за формулою: РБ = безробітні / (зайняті + безробітні).
"""
if unemployed + employed == 0:
raise ZeroDivisionError("Загальна кількість людей не може бути нульовою.")
return unemployed / (unemployed + employed)
try:
unemployed = int(input("Введіть кількість безробітних (людей): "))
employed = int(input("Введіть кількість зайнятих (людей): "))
rate = unemployment_rate(unemployed, employed)
print(f"Рівень безробіття = {rate:.1%}")
except ValueError:
print("Помилка: кількість людей має бути цілим числом.")
except ZeroDivisionError as e:
print(f"Помилка розрахунку: {e}")
except Exception as e:
print(f"Виникла непередбачена помилка: {e}")
```
<!--

-->
:::
### Задача 7
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): !!!
"""
def power(x, y=2):
"""Повернути x^y."""
if y == 0:
return 1
else:
return x * power(x, y - 1)
x = int(input("x="))
y = int(input("y="))
print(power(x, y))
```
:::spoiler Підказка
1. **Аналіз потенційних помилок**:
* Функція `power` є рекурсивною. Якщо `y` буде великим, глибина рекурсії може перевищити ліміт, що спричинить `RecursionError`.
* Користувач може ввести нечислові дані, що викличе `ValueError`.
2. **Обробка помилок**:
* Оберніть блок введення даних та виклику функції `power` у `try...except`.
* Додайте обробник для `ValueError`, щоб повідомити про некоректне введення.
* Додайте обробник для `RecursionError`, щоб повідомити про завелике значення `y`.
* Додайте загальний `except Exception` для інших помилок.
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): 11 14 15
"""
def power(x, y=2):
"""Повернути x^y."""
if y == 0:
return 1
else:
return x * power(x, y - 1)
try:
x = int(input("x="))
y = int(input("y="))
print(power(x, y))
except ValueError:
print("Помилка: x та y мають бути цілими числами.")
except RecursionError:
print("Помилка: занадто велике значення y для обчислення.")
except Exception as e:
print(f"Виникла непередбачена помилка: {e}")
```
<!--

-->
:::
### Задача 8
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2):
"""
# Наведено список ПІБ. Знайдіть найпоширеніше по-батькові.
# Якщо немає по-батькові, людина не враховується в розрахунку.
n = int(input("Введіть кількість людей: "))
middle_names = {}
for i in range(n):
fio = input("Введіть ПІБ через пробіл: ").split()
middle_name = fio[2]
middle_names[middle_name] = middle_names.get(middle_name, 0) + 1
print(sorted(middle_names.items(), key=lambda item: item[1])[-1][0])
print("Людей брало участь у розрахунку:", n)
```
:::spoiler Підказка
1. **Аналіз потенційних помилок**:
* Якщо користувач введе неповне ПІБ (менше трьох слів), спроба доступу до `fio[2]` викличе `IndexError`.
* Якщо після обробки всіх введених даних словник `middle_names` залишиться порожнім (наприклад, ніхто не ввів по-батькові), спроба доступу до `[-1]` у порожньому списку також викличе `IndexError`.
* Введення кількості людей `n` може спричинити `ValueError`.
2. **Обробка помилок**:
* Всередині циклу `for` оберніть рядок `middle_name = fio[2]` у блок `try...except IndexError`. У блоці `except` можна пропустити ітерацію (`continue`).
* Після циклу, перед виведенням результату, перевірте, чи не порожній словник `middle_names`. Якщо він порожній, виведіть відповідне повідомлення.
* Оберніть початкове введення `n` у `try...except ValueError`.
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): 9 15 18
"""
# Наведено список ПІБ. Знайдіть найпоширеніше по-батькові.
# Якщо немає по-батькові, людина не враховується в розрахунку.
try:
n = int(input("Введіть кількість людей: "))
except ValueError:
print("Кількість людей має бути числом.")
n = 0
middle_names = {}
count = 0
for i in range(n):
fio = input("Введіть ПІБ через пробіл: ").split()
try:
middle_name = fio[2]
middle_names[middle_name] = middle_names.get(middle_name, 0) + 1
count += 1
except IndexError:
print("Попередження: ПІБ введено не повністю, запис проігноровано.")
if middle_names:
most_common = sorted(middle_names.items(), key=lambda item: item[1])[-1][0]
print("Найпоширеніше по-батькові:", most_common)
else:
print("Не було введено жодного повного ПІБ.")
print("Людей брало участь у розрахунку:", count)
```
<!--

-->
:::
### Задача 9
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): !!!
"""
def f(x):
"""Повернути значення функції.
Функція не обробляє винятки.
"""
return x**2 / (x + 2) - 3
k = int(input("Введіть межу інтервалу [-k; k]: "))
h = float(input("Введіть крок: "))
x = -k
print("x".rjust(10), "f(x)".rjust(10))
while x <= k:
# Якщо помилка виникає під час обчислення функції,
# Необхідно вивести тире "-"
# Цей випадок повинен обробити другий вкладений блок try
print(f"{x:10.2f} {f(x):10.2f}")
x += h
# --------------
# Приклад роботи:
#
# Введіть межу інтервалу [-k; k]: 5
# Введіть крок: 0.5
# x f(x)
# -5.00 -11.33
# -4.50 -11.10
# -4.00 -11.00
# -3.50 -11.17
# -3.00 -12.00
# -2.50 -15.50
# -2.00 -
# -1.50 1.50
# -1.00 -2.00
# ...
# 4.50 0.12
# 5.00 0.57
```
:::spoiler Підказка
1. **Аналіз потенційних помилок**:
* Функція `f(x)` містить ділення на `x + 2`. Якщо `x` дорівнюватиме `-2`, виникне `ZeroDivisionError`.
* При введенні меж інтервалу та кроку користувач може ввести нечислові дані, що спричинить `ValueError`.
2. **Обробка помилок**:
* Оберніть блок введення даних у `try...except ValueError`.
* Всередині циклу `while` оберніть виклик функції `print(f"{x:10.2f} {f(x):10.2f}")` у вкладений блок `try...except ZeroDivisionError`.
* У блоці `except` для `ZeroDivisionError` виведіть рядок з прочерком замість значення функції, як зазначено в умові.
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): 11 14 15
"""
def f(x):
"""Повернути значення функції.
Функція не обробляє винятки.
"""
return x**2 / (x + 2) - 3
try:
k = int(input("Введіть межу інтервалу [-k; k]: "))
h = float(input("Введіть крок: "))
x = -k
print("x".rjust(10), "f(x)".rjust(10))
while x <= k:
# Якщо помилка виникає під час обчислення функції,
# Необхідно вивести тире "-"
# Цей випадок повинен обробити другий вкладений блок try
try:
print(f"{x:10.2f} {f(x):10.2f}")
except ZeroDivisionError:
print(f"{x:10.2f} {'-':>10}")
x += h
except ValueError:
print("Помилка: межа та крок мають бути числами.")
except Exception as e:
print(f"Виникла непередбачена помилка: {e}")
```
<!--

-->
:::
### Задача 10
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): !!!
"""
# Програма містить словник результатів НМТ іспит = бал.
# Дізнайтеся, чи заявник зможе вступити, якщо вам потрібно сдати
# Усі ці іспити не менше мінімальної кількості балів.
min_grades = {"Історія": 180, "Математика": 185, "Українська мова": 175}
print(
"""Щоб визначити можливість вступу, необхідна інформація про вас.
Щоб ввести іспит і бали, введіть їх через |: Історія | 140.
Щоб завершити введення, натисніть кнопку Enter.
"""
)
exams = {}
while True:
line = input("").strip()
if line == "":
break
exam, grade = [x.strip() for x in line.split("|")]
exams[exam] = int(grade)
print("Ваші іспити:")
for i, (exam, grade) in enumerate(exams.items(), start=1):
print(f"{i}) {exam} {grade}")
ok = False
for exam_name, min_grade in min_grades.items():
if exams[exam_name] < min_grade:
break
else:
ok = True
print("Ви можете вступити до нас!" if ok else "На жаль...")
```
:::spoiler Підказка
1. **Аналіз потенційних помилок**:
* При введенні даних `exam, grade = ... line.split("|")` може виникнути `ValueError`, якщо рядок не містить `|` або містить їх забагато.
* При перетворенні `int(grade)` може виникнути `ValueError`, якщо бал не є числом.
* У циклі перевірки `if exams[exam_name] < min_grade:` може виникнути `KeyError`, якщо абітурієнт не ввів результати по одному з обов'язкових іспитів.
2. **Обробка помилок**:
* Всередині циклу `while True` оберніть блок обробки рядка у `try...except ValueError`.
* Оберніть цикл перевірки результатів у `try...except KeyError`. Якщо виникає `KeyError`, це означає, що не всі іспити складено, тому `ok` має бути `False`.
```python=
"""
Помилки (номери рядків через пробіл, цей рядок - №2): 25 26 34
"""
# Програма містить словник результатів НМТ іспит = бал.
# Дізнайтеся, чи заявник зможе вступити, якщо вам потрібно сдати
# Усі ці іспити не менше мінімальної кількості балів.
min_grades = {"Історія": 180, "Математика": 185, "Українська мова": 175}
print(
"""Щоб визначити можливість вступу, необхідна інформація про вас.
Щоб ввести іспит і бали, введіть їх через |: Історія | 140.
Щоб завершити введення, натисніть кнопку Enter.
"""
)
exams = {}
while True:
line = input("").strip()
if line == "":
break
try:
exam, grade = [x.strip() for x in line.split("|")]
exams[exam] = int(grade)
except ValueError:
print("Помилка формату. Введіть дані як 'Іспит | Бал'." )
print("Ваші іспити:")
for i, (exam, grade) in enumerate(exams.items(), start=1):
print(f"{i}) {exam} {grade}")
ok = False
try:
for exam_name, min_grade in min_grades.items():
if exams[exam_name] < min_grade:
break
else:
ok = True
except KeyError as e:
print(f"Відсутня інформація про обов'язковий іспит: {e}")
print("Ви можете вступити до нас!" if ok else "На жаль...")
```
<!--

-->
:::
## Підвищений рівень. Створення власних винятків
### Задача 11
Вихідна програма:
```python=
"""
Помилки (номери рядків через пробіл): !!!
"""
class NoMoneyToWithdrawError(Exception):
pass
class PaymentError(Exception):
pass
def print_accounts(accounts):
"""Друк акаунтів."""
print(f"Список клієнтів ({len(accounts)}): ")
for i, (name, value) in enumerate(accounts.items(), start=1):
print(f"{i}. {name} {value}")
def transfer_money(accounts, account_from, account_to, value):
"""Виконати переказ 'value' грошей з рахунку 'account_from' на 'account_to'.
При переказі коштів необхідно враховувати:
- чи вистачає грошей на рахунку, з якого здійснюється переказ;
- переказ складається зі зменшення балансу першого рахунку та збільшення
балансу другого; якщо хоча б на одному етапі відбувається помилка,
облікові записи повинні бути приведені в початковий стан
(механізм транзакції)
см. https://uk.wikipedia.org/wiki/Транзакція_(бази_даних)
Винятки (raise):
- NoMoneyToWithdrawError: на рахунку 'account_from'
не вистачає грошей для переказу;
- PaymentError: помилка під час переказу.
"""
# Видаліть коментар та допишіть код
if __name__ == "__main__":
accounts = {
"Василь Іванов": 100,
"Іван Васильєв": 1500,
"Петро Бондаренко": 400
}
print_accounts(accounts)
payment_info = {
"account_from": "Василь Іванов",
"account_to": "Іван Васильєв"
}
print("Переказ від {account_from} для {account_to}...".
format(**payment_info))
payment_info["value"] = int(input("Сума = "))
transfer_money(accounts, **payment_info)
print("OK!")
print_accounts(accounts)
```
:::spoiler Підказка
1. **Реалізація `transfer_money`**:
* Спочатку перевірте, чи достатньо коштів на рахунку `account_from`. Якщо ні, згенеруйте виняток `NoMoneyToWithdrawError`.
* Збережіть початкові баланси рахунків `account_from` та `account_to`.
* Оберніть операції зі зміни балансів у блок `try...except`. Це потрібно для реалізації механізму транзакції.
* У блоці `try` зменште баланс `account_from` і збільште баланс `account_to`.
* У блоці `except` відновіть початкові баланси та згенеруйте новий виняток `PaymentError`, щоб повідомити про невдалу транзакцію.
2. **Обробка винятків у головному коді**:
* Оберніть виклик `transfer_money` у блок `try...except`.
* Додайте обробники для `NoMoneyToWithdrawError` та `PaymentError`, виводячи відповідні повідомлення.
* Також обробіть `ValueError` для введення суми та `KeyError`, якщо вказаних рахунків не існує.
```python=
"""
Помилки (номери рядків через пробіл): 56 58
"""
class NoMoneyToWithdrawError(Exception):
pass
class PaymentError(Exception):
pass
def print_accounts(accounts):
"""Друк акаунтів."""
print(f"Список клієнтів ({len(accounts)}): ")
for i, (name, value) in enumerate(accounts.items(), start=1):
print(f"{i}. {name} {value}")
def transfer_money(accounts, account_from, account_to, value):
"""Виконати переказ 'value' грошей з рахунку 'account_from' на 'account_to'.
При переказі коштів необхідно враховувати:
- чи вистачає грошей на рахунку, з якого здійснюється переказ;
- переказ складається зі зменшення балансу першого рахунку та збільшення
балансу другого; якщо хоча б на одному етапі відбувається помилка,
облікові записи повинні бути приведені в початковий стан
(механізм транзакції)
см. https://uk.wikipedia.org/wiki/Транзакція_(бази_даних)
Винятки (raise):
- NoMoneyToWithdrawError: на рахунку 'account_from'
не вистачає грошей для переказу;
- PaymentError: помилка під час переказу.
"""
if accounts[account_from] < value:
raise NoMoneyToWithdrawError(f"На рахунку {account_from} недостатньо коштів.")
# Зберігаємо стан для можливого відкату
original_from_balance = accounts[account_from]
original_to_balance = accounts[account_to]
try:
accounts[account_from] -= value
# Для імітації помилки можна додати:
# if account_to == "Іван Васильєв":
# raise Exception("Помилка зв'язку з банком одержувача")
accounts[account_to] += value
except Exception as e:
# Відкат транзакції
accounts[account_from] = original_from_balance
accounts[account_to] = original_to_balance
raise PaymentError(f"Не вдалося виконати переказ. Причина: {e}")
if __name__ == "__main__":
accounts = {
"Василь Іванов": 100,
"Іван Васильєв": 1500,
"Петро Бондаренко": 400
}
print_accounts(accounts)
payment_info = {
"account_from": "Василь Іванов",
"account_to": "Іван Васильєв"
}
print("Переказ від {account_from} для {account_to}...".
format(**payment_info))
try:
payment_info["value"] = int(input("Сума = "))
transfer_money(accounts, **payment_info)
print("OK!")
except ValueError:
print("Помилка: сума має бути цілим числом.")
except KeyError as e:
print(f"Помилка: рахунок {e} не знайдено.")
except NoMoneyToWithdrawError as e:
print(f"Помилка переказу: {e}")
except PaymentError as e:
print(f"Помилка платежу: {e}")
except Exception as e:
print(f"Виникла непередбачена помилка: {e}")
finally:
print_accounts(accounts)
```
<!--
-->
:::
### Задача 12
Створіть систему валідації користувацьких даних з власними винятками.
Вихідна програма:
```python=
"""
Створити систему реєстрації користувача з перевіркою даних.
Використати власні винятки для різних типів помилок.
"""
class ValidationError(Exception):
"""Базовий клас для помилок валідації."""
pass
class InvalidEmailError(ValidationError):
"""Виняток для невалідної електронної пошти."""
pass
class WeakPasswordError(ValidationError):
"""Виняток для слабкого пароля."""
pass
class InvalidAgeError(ValidationError):
"""Виняток для невалідного віку."""
pass
def validate_email(email):
"""Перевірити формат електронної пошти.
Параметри:
- email (str): адреса електронної пошти.
Винятки:
- InvalidEmailError: якщо email не містить '@' або '.':
"""
# Допишіть код
def validate_password(password):
"""Перевірити надійність пароля.
Параметри:
- password (str): пароль користувача.
Винятки:
- WeakPasswordError: якщо пароль коротший 8 символів
або не містить цифр.
"""
# Допишіть код
def validate_age(age):
"""Перевірити вік користувача.
Параметри:
- age (int): вік користувача.
Винятки:
- InvalidAgeError: якщо вік менше 14 або більше 120.
"""
# Допишіть код
def register_user(email, password, age):
"""Зареєструвати користувача після валідації всіх даних.
Параметри:
- email (str): електронна пошта.
- password (str): пароль.
- age (int): вік.
Повертає:
- dict: дані користувача.
"""
# Допишіть код
if __name__ == "__main__":
print("=== Реєстрація користувача ===")
email = input("Email: ")
password = input("Пароль: ")
age_str = input("Вік: ")
# Додайте обробку винятків
# Допишіть код
```
:::spoiler Підказка
1. **Реалізація функцій валідації**:
* У `validate_email` перевірте наявність '@' та '.' у рядку. Якщо їх немає, згенеруйте `InvalidEmailError`.
* У `validate_password` перевірте довжину (>= 8) та наявність хоча б однієї цифри (`any(c.isdigit() for c in password)`). Якщо умови не виконані, згенеруйте `WeakPasswordError`.
* У `validate_age` перевірте діапазон 14-120. Якщо вік поза діапазоном, згенеруйте `InvalidAgeError`.
2. **Реалізація `register_user`**:
* Послідовно викликайте всі три функції валідації.
* Якщо всі перевірки пройдено, поверніть словник з даними користувача.
3. **Обробка винятків у головному коді**:
* Оберніть код реєстрації у `try...except`.
* Додайте окремі обробники для `InvalidEmailError`, `WeakPasswordError`, `InvalidAgeError`.
* Обробіть `ValueError` для перетворення віку.
* Додайте загальний обробник для інших винятків.
```python=
"""
Створити систему реєстрації користувача з перевіркою даних.
Використати власні винятки для різних типів помилок.
"""
class ValidationError(Exception):
"""Базовий клас для помилок валідації."""
pass
class InvalidEmailError(ValidationError):
"""Виняток для невалідної електронної пошти."""
pass
class WeakPasswordError(ValidationError):
"""Виняток для слабкого пароля."""
pass
class InvalidAgeError(ValidationError):
"""Виняток для невалідного віку."""
pass
def validate_email(email):
"""Перевірити формат електронної пошти.
Параметри:
- email (str): адреса електронної пошти.
Винятки:
- InvalidEmailError: якщо email не містить '@' або '.':
"""
if '@' not in email or '.' not in email:
raise InvalidEmailError("Email має містити символи '@' та '.'")
def validate_password(password):
"""Перевірити надійність пароля.
Параметри:
- password (str): пароль користувача.
Винятки:
- WeakPasswordError: якщо пароль коротший 8 символів
або не містить цифр.
"""
if len(password) < 8:
raise WeakPasswordError("Пароль має містити щонайменше 8 символів")
if not any(c.isdigit() for c in password):
raise WeakPasswordError("Пароль має містити щонайменше одну цифру")
def validate_age(age):
"""Перевірити вік користувача.
Параметри:
- age (int): вік користувача.
Винятки:
- InvalidAgeError: якщо вік менше 14 або більше 120.
"""
if age < 14 or age > 120:
raise InvalidAgeError("Вік має бути в діапазоні від 14 до 120 років")
def register_user(email, password, age):
"""Зареєструвати користувача після валідації всіх даних.
Параметри:
- email (str): електронна пошта.
- password (str): пароль.
- age (int): вік.
Повертає:
- dict: дані користувача.
"""
validate_email(email)
validate_password(password)
validate_age(age)
return {
"email": email,
"age": age,
"status": "зареєстровано"
}
if __name__ == "__main__":
print("=== Реєстрація користувача ===")
email = input("Email: ")
password = input("Пароль: ")
age_str = input("Вік: ")
try:
age = int(age_str)
user = register_user(email, password, age)
print(f"\n✓ Користувача успішно зареєстровано!")
print(f" Email: {user['email']}")
print(f" Вік: {user['age']}")
except ValueError:
print("✗ Помилка: вік має бути цілим числом")
except InvalidEmailError as e:
print(f"✗ Помилка email: {e}")
except WeakPasswordError as e:
print(f"✗ Помилка пароля: {e}")
except InvalidAgeError as e:
print(f"✗ Помилка віку: {e}")
except ValidationError as e:
print(f"✗ Помилка валідації: {e}")
except Exception as e:
print(f"✗ Непередбачена помилка: {e}")
```
<!--
-->
:::
### Задача 13
Створіть систему управління бібліотечним каталогом з обробкою складних ситуацій.
Вихідна програма:
```python=
"""
Створити систему управління бібліотекою з можливістю
видачі та повернення книг з обробкою різних виняткових ситуацій.
"""
class LibraryError(Exception):
"""Базовий клас для помилок бібліотеки."""
pass
class BookNotFoundError(LibraryError):
"""Виняток: книга не знайдена в каталозі."""
pass
class BookNotAvailableError(LibraryError):
"""Виняток: всі примірники книги видані."""
pass
class BookAlreadyReturnedError(LibraryError):
"""Виняток: книга вже повернута або не була видана."""
pass
class InvalidQuantityError(LibraryError):
"""Виняток: некоректна кількість книг."""
pass
class Library:
"""Клас для управління бібліотекою."""
def __init__(self):
"""Ініціалізація бібліотеки.
Структура каталогу:
{
"назва_книги": {
"total": загальна_кількість,
"available": доступна_кількість,
"borrowed_by": [список_користувачів]
}
}
"""
self.catalog = {}
def add_book(self, title, quantity=1):
"""Додати книгу до каталогу.
Параметри:
- title (str): назва книги.
- quantity (int): кількість примірників.
Винятки:
- InvalidQuantityError: якщо quantity <= 0.
"""
# Допишіть код
def borrow_book(self, title, user):
"""Видати книгу користувачу.
Параметри:
- title (str): назва книги.
- user (str): ім'я користувача.
Винятки:
- BookNotFoundError: книга відсутня в каталозі.
- BookNotAvailableError: всі примірники видані.
"""
# Допишіть код
def return_book(self, title, user):
"""Прийняти повернення книги.
Параметри:
- title (str): назва книги.
- user (str): ім'я користувача.
Винятки:
- BookNotFoundError: книга відсутня в каталозі.
- BookAlreadyReturnedError: користувач не брав цю книгу.
"""
# Допишіть код
def show_catalog(self):
"""Показати весь каталог."""
# Допишіть код
if __name__ == "__main__":
library = Library()
# Приклад використання
print("=== Система управління бібліотекою ===\n")
# Додайте обробку винятків для наступних операцій:
# 1. Додати книги до каталогу
# 2. Видати книгу користувачу
# 3. Повернути книгу
# 4. Показати каталог
# Допишіть код
```
:::spoiler Підказка
1. **Реалізація методу `add_book`**:
* Перевірте, що `quantity > 0`, інакше згенеруйте `InvalidQuantityError`.
* Якщо книга вже є в каталозі, збільште `total` та `available` на `quantity`.
* Якщо книга нова, створіть новий запис у словнику.
2. **Реалізація методу `borrow_book`**:
* Перевірте наявність книги в каталозі, інакше згенеруйте `BookNotFoundError`.
* Перевірте, що є доступні примірники (`available > 0`), інакше згенеруйте `BookNotAvailableError`.
* Зменште `available` на 1 та додайте користувача до списку `borrowed_by`.
3. **Реалізація методу `return_book`**:
* Перевірте наявність книги в каталозі.
* Перевірте, що користувач є у списку `borrowed_by`, інакше згенеруйте `BookAlreadyReturnedError`.
* Збільште `available` на 1 та видаліть користувача зі списку.
4. **Головний код**:
* Оберніть всі операції з бібліотекою у `try...except` блоки.
* Створіть окремі обробники для кожного типу винятків.
<!--
```python=
"""
Створити систему управління бібліотекою з можливістю
видачі та повернення книг з обробкою різних виняткових ситуацій.
"""
class LibraryError(Exception):
"""Базовий клас для помилок бібліотеки."""
pass
class BookNotFoundError(LibraryError):
"""Виняток: книга не знайдена в каталозі."""
pass
class BookNotAvailableError(LibraryError):
"""Виняток: всі примірники книги видані."""
pass
class BookAlreadyReturnedError(LibraryError):
"""Виняток: книга вже повернута або не була видана."""
pass
class InvalidQuantityError(LibraryError):
"""Виняток: некоректна кількість книг."""
pass
class Library:
"""Клас для управління бібліотекою."""
def __init__(self):
"""Ініціалізація бібліотеки.
Структура каталогу:
{
"назва_книги": {
"total": загальна_кількість,
"available": доступна_кількість,
"borrowed_by": [список_користувачів]
}
}
"""
self.catalog = {}
def add_book(self, title, quantity=1):
"""Додати книгу до каталогу.
Параметри:
- title (str): назва книги.
- quantity (int): кількість примірників.
Винятки:
- InvalidQuantityError: якщо quantity <= 0.
"""
if quantity <= 0:
raise InvalidQuantityError("Кількість книг має бути більше 0")
if title in self.catalog:
self.catalog[title]["total"] += quantity
self.catalog[title]["available"] += quantity
else:
self.catalog[title] = {
"total": quantity,
"available": quantity,
"borrowed_by": []
}
print(f"✓ Додано '{title}' ({quantity} шт.)")
def borrow_book(self, title, user):
"""Видати книгу користувачу.
Параметри:
- title (str): назва книги.
- user (str): ім'я користувача.
Винятки:
- BookNotFoundError: книга відсутня в каталозі.
- BookNotAvailableError: всі примірники видані.
"""
if title not in self.catalog:
raise BookNotFoundError(f"Книга '{title}' відсутня в каталозі")
if self.catalog[title]["available"] == 0:
raise BookNotAvailableError(
f"Всі примірники '{title}' видані. "
f"Книгу взяли: {', '.join(self.catalog[title]['borrowed_by'])}"
)
self.catalog[title]["available"] -= 1
self.catalog[title]["borrowed_by"].append(user)
print(f"✓ '{title}' видано користувачу {user}")
def return_book(self, title, user):
"""Прийняти повернення книги.
Параметри:
- title (str): назва книги.
- user (str): ім'я користувача.
Винятки:
- BookNotFoundError: книга відсутня в каталозі.
- BookAlreadyReturnedError: користувач не брав цю книгу.
"""
if title not in self.catalog:
raise BookNotFoundError(f"Книга '{title}' відсутня в каталозі")
if user not in self.catalog[title]["borrowed_by"]:
raise BookAlreadyReturnedError(
f"Користувач {user} не брав книгу '{title}' "
f"або вже її повернув"
)
self.catalog[title]["available"] += 1
self.catalog[title]["borrowed_by"].remove(user)
print(f"✓ Користувач {user} повернув '{title}'")
def show_catalog(self):
"""Показати весь каталог."""
if not self.catalog:
print("Каталог порожній")
return
print("\n=== Каталог бібліотеки ===")
for i, (title, info) in enumerate(self.catalog.items(), start=1):
status = f"{info['available']}/{info['total']} доступно"
borrowed = ""
if info['borrowed_by']:
borrowed = f" (взяли: {', '.join(info['borrowed_by'])})"
print(f"{i}. {title}: {status}{borrowed}")
print()
if __name__ == "__main__":
library = Library()
print("=== Система управління бібліотекою ===\n")
# Демонстрація роботи з обробкою винятків
# 1. Додавання книг
try:
library.add_book("Python для початківців", 3)
library.add_book("Алгоритми та структури даних", 2)
library.add_book("Чисте програмування", 1)
except InvalidQuantityError as e:
print(f"✗ Помилка: {e}")
library.show_catalog()
# 2. Видача книг
try:
library.borrow_book("Python для початківців", "Іван")
library.borrow_book("Python для початківців", "Марія")
library.borrow_book("Чисте програмування", "Петро")
except BookNotFoundError as e:
print(f"✗ {e}")
except BookNotAvailableError as e:
print(f"✗ {e}")
library.show_catalog()
# 3. Спроба взяти книгу, якої немає в наявності
try:
library.borrow_book("Чисте програмування", "Олена")
except BookNotAvailableError as e:
print(f"✗ {e}\n")
# 4. Повернення книги
try:
library.return_book("Python для початківців", "Іван")
except BookAlreadyReturnedError as e:
print(f"✗ {e}")
except BookNotFoundError as e:
print(f"✗ {e}")
library.show_catalog()
# 5. Спроба повернути книгу, яку не брали
try:
library.return_book("Python для початківців", "Іван")
except BookAlreadyReturnedError as e:
print(f"✗ {e}\n")
# 6. Спроба роботи з неіснуючою книгою
try:
library.borrow_book("Неіснуюча книга", "Василь")
except BookNotFoundError as e:
print(f"✗ {e}\n")
# Загальна обробка помилок
try:
library.add_book("Нова книга", -5)
except LibraryError as e:
print(f"✗ Помилка бібліотеки: {e}")
except Exception as e:
print(f"✗ Непередбачена помилка: {e}")
```
-->
:::
---
(c) Яценко Р.М., 2021-2026 [Python Advanced]
[Навчальний центр комп'ютерних технологій](http://kit.kh.ua/) [](http://kit.kh.ua/)