--- 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 Виправлена програма: ![](https://i.imgur.com/k1wPtkW.png) <!-- --> ::: ### Задача 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 Виправлена програма: ![](https://i.imgur.com/ITGKYen.png) <!-- --> ::: ### Задача 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 Виправлена програма: ![](https://i.imgur.com/FKAZAqf.jpg) <!-- --> ::: ### Задача 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 Виправлена програма: ![](https://i.imgur.com/JYL9JsK.png) <!-- --> ::: ### Задача 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 Виправлена програма: ![](https://i.imgur.com/ljGm2pA.png) <!-- --> ::: ## Середній рівень. Обробка винятків та 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}") ``` <!-- ![image](https://hackmd.io/_uploads/BkxRZJHFkg.png) --> ::: ### Задача 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}") ``` <!-- ![image](https://hackmd.io/_uploads/SJdXXyHY1e.png) --> ::: ### Задача 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) ``` <!-- ![image](https://hackmd.io/_uploads/Bkgi2yBtJe.png) --> ::: ### Задача 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}") ``` <!-- ![image](https://hackmd.io/_uploads/rkynmeBY1l.png) --> ::: ### Задача 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 "На жаль...") ``` <!-- ![image](https://hackmd.io/_uploads/SkxVUdxSt1g.png) --> ::: ## Підвищений рівень. Створення власних винятків ### Задача 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/) [![kit](https://i.imgur.com/Kh901c1.png =10%x)](http://kit.kh.ua/)