Различни грешки и finally
Проблем
Сметката за нула души
Програмата дели сметката в ресторанта по равно. Вече знаеш за грешния вход и го хващаш:
try: bill = int(input()) people = int(input()) print(f"На човек: {bill // people} €")except ValueError: print("Въведи цели числа.")Сметката е 90 €, а някой въвежда 0 за броя на хората. Програмата пак спира:
Traceback (most recent call last): File "/projects/bill/bill.py", line 4, in <module> print(f"На човек: {bill // people} €") ~~~~~^^~~~~~~~ZeroDivisionError: integer division or modulo by zeroexcept е там, но не помага. Хваща само ValueError, а това е друг вид изключение.
Идеята
Няколко except
След един try може да има няколко except, по един за всеки вид грешка:
try: bill = int(input()) people = int(input()) print(f"На човек: {bill // people} €")except ValueError: print("Въведи цели числа.")except ZeroDivisionError: print("Хората не може да са 0.")| Вход | Изход |
|---|---|
| 90 и 3 | На човек: 30 € |
| 90 и 0 | Хората не може да са 0. |
| 90 и трима | Въведи цели числа. |
Блоковете се проверяват отгоре надолу. Изпълнява се първият, чийто вид отговаря, и само той. Останалите се прескачат.
try: bill = int(input()) people = int(input()) print(f"На човек: {bill // people} €")except ValueError: print("Въведи цели числа.")except ZeroDivisionError: print("Хората не може да са 0.")Стъпка по стъпка, с думи
- Първо пускане: сметка 90 €, хората са 0.
- 90 / 0 е деление на нула: хвърля се ZeroDivisionError.
- Първият except е за ValueError. Не пасва, прескача се.
- Вторият пасва. Изпълнява се той и само той.
- Второ пускане: вместо брой хора е написано „трима“.
- int() не може да направи число: хвърля се ValueError.
- Пасва още първият except. До втория не се стига.
- Блоковете се проверяват отгоре надолу. Изпълнява се първият, който пасва.
По-точно, за любопитните
- Когато два вида се обработват еднакво, можеш да ги хванеш в един блок с кортеж:
except (ValueError, ZeroDivisionError):.
Идеята
Семейството на изключенията
Изключенията са класове, свързани с наследяване. Ето най-честите:
BaseException├── KeyboardInterrupt Ctrl+C, не се хваща├── SystemExit sys.exit(), не се хваща└── Exception ├── ArithmeticError │ ├── ZeroDivisionError │ └── OverflowError ├── LookupError │ ├── IndexError │ └── KeyError ├── ValueError ├── TypeError └── OSError файлове, мрежа └── FileNotFoundErrorexcept хваща посочения вид и всичките му наследници. Това е полиморфизмът от модула за наследяването: обект на наследника е и от вида на родителя. issubclass го показва:
print(issubclass(ZeroDivisionError, ArithmeticError))print(issubclass(IndexError, LookupError))print(issubclass(ValueError, Exception))print(issubclass(KeyboardInterrupt, Exception))TrueTrueTrueFalseЗатова except Exception as e: хваща почти всичко:
def split(bill_text, people_text): try: bill = int(bill_text) people = int(people_text) print(f"На човек: {bill // people} €") except Exception as e: print(f"{type(e).__name__}: {e}")
split("90", "3")split("90", "0")split("90", "трима")На човек: 30 €ZeroDivisionError: integer division or modulo by zeroValueError: invalid literal for int() with base 10: 'трима'type(e).__name__ дава името на истинския клас на обекта. except е за Exception, но обектът е от наследник.
По-точно, за любопитните
KeyboardInterrupt(Ctrl+C) иSystemExit(sys.exit()) не са наследници наException, а наBaseException. Такаexcept Exception:не пречи на програмата да бъде спряна.except:без вид иexcept BaseException:хващат и тях. Не ги пиши.
- Хващай толкова конкретно, колкото можеш. except Exception: скрива и грешки, които не си очаквал, например бъг в собствения ти код.
Пробвай
Кой catch?
Какво ще отпечата програмата?
seats = [4, 2, 6]try: print(f"Маса 4: {seats[3]} места")except ZeroDivisionError: print("Деление на нула")except LookupError: print("Грешка при търсене")except Exception: print("Обща грешка")seats[3] е извън списъка с 3 елемента: IndexError. Той не е ZeroDivisionError, затова първият блок се прескача. Той е наследник на LookupError, затова вторият отговаря и се изпълнява. До третия не се стига, макар че и той би отговарял.Пробвай
Намери грешката
text = "0" програмата печата „Нещо се обърка.“, а не „Хората не може да са 0.“. На кой ред е грешката?Избери реда с грешката.
Exception е родител на ZeroDivisionError. Първият except хваща всичко, а вторият никога не може да се стигне. Python не казва нищо: програмата върви и просто печата общото съобщение. Правилото е от конкретното към общото: размени двата блока.Идеята
Никой не задължава
В Java има проверени изключения: компилаторът не приема програмата, докато не ги хванеш или не ги обявиш с throws. В Python такива няма. Нито едно изключение не е задължително за хващане:
import time
def print_receipt(): print("Печатам бележката...") time.sleep(0.3) print("Готово.")
print_receipt()Печатам бележката...Готово.import time взима модула за времето, а time.sleep(0.3) спира програмата за 0,3 секунди. Ако по време на паузата натиснеш Ctrl+C, Python хвърля KeyboardInterrupt, но никой не иска да го хванеш. Същото е за int() и ValueError.
Затова отговорността е твоя. Кои изключения хвърля една функция, пише в документацията ѝ, обикновено с думата „Raises“. Прочети я, преди да решиш дали да хващаш.
Идеята
finally: винаги
Принтерът на касата трябва да се освободи след всяка бележка, иначе следващата няма да се отпечата. Каквото и да стане, успех или грешка:
def print_receipt(qty_text): print("Принтерът е зает.") try: qty = int(qty_text) print(f"Кафе: {qty} x 2 € = {qty * 2} €") except ValueError: print(f"Грешно количество: {qty_text}") finally: print("Принтерът е свободен.")
print_receipt("3")print_receipt("три")Принтерът е зает.Кафе: 3 x 2 € = 6 €Принтерът е свободен.Принтерът е зает.Грешно количество: триПринтерът е свободен.finally се изпълнява винаги: когато всичко мине, когато изключението е хванато и дори когато нито един от тези except не го хваща. В последния случай finally се изпълнява, а изключението продължава нагоре към следващия try, който може да го хване.
Защо не просто ред след try/except? Защото той се пропуска, ако изключението е от вид, който except не хваща, или ако в try има return. finally не се пропуска.
В Python има и else: той се изпълнява само ако в try не е хвърлено изключение. Така в try остава само редът, който може да хвърли, а печатането е след него:
def print_receipt(qty_text): print("Принтерът е зает.") try: qty = int(qty_text) except ValueError: print(f"Грешно количество: {qty_text}") else: print(f"Кафе: {qty} x 2 € = {qty * 2} €") finally: print("Принтерът е свободен.")
print_receipt("3")print_receipt("три")Принтерът е зает.Кафе: 3 x 2 € = 6 €Принтерът е свободен.Принтерът е зает.Грешно количество: триПринтерът е свободен.По-точно, за любопитните
- Може и
tryсfinally, безexcept: изключението не се хваща, но почистването се изпълнява.
- За ресурси, които трябва да се затварят, като файлове и връзки с база данни, има по-кратък запис:
with. Ще го видим, когато стигнем до файловете. Той сам затваря ресурса, както би го затворилfinally.
finallyсе изпълнява дори приsys.exit(): в Python и това е изключение (SystemExit), което минава презfinallyнагоре.
Пробвай
finally и return
Какво ще отпечата програмата?
def reserve(guests): try: if guests > 6: return f"Няма маса за {guests}" return f"Маса за {guests}" finally: print("Записах опита.")
print(reserve(8))return в try не прескача finally. Функцията първо изпълнява finally, после връща текста. Затова „Записах опита.“ се отпечатва преди резултата, който print получава чак когато функцията свърши.
Упражнение
Програми с няколко except
Реши задачите в редактора си. Не печатай въпроси.
1. Цена на човек. Цените са в списък [120, 75, 300]. Прочети номер на продукт (от 1) и броя на хората и отпечатай цената на човек с целочислено деление. Хвани три неща: несъществуващ продукт, нула души и всичко останало (тогава отпечатай съобщението на изключението).
| Вход | Изход |
|---|---|
| 2 и 3 | На човек: 25 € |
| 5 и 2 | Няма такъв продукт. |
| 1 и 0 | Броят на хората не може да е 0. |
| 1 и двама | Грешка: invalid literal for int() with base 10: 'двама' |
2. Калкулатор. Прочети цяло число, оператор (+, -, * или /) и второ цяло число, всяко на свой ред. Отпечатай сметката (делението е //). При текст вместо число кажи „Въведи цели числа.“, при деление на нула „Деление на нула.“, а при друг оператор „Непознат оператор: “ и оператора. За оператора е удобен match.
84/484 / 4 = 2184/0Деление на нула.Още задачи
- Терминалът. Функция
pay(amount_text)печата „Терминалът е свързан.“, после „Платени X €“ или съобщение за грешен вход, а въвfinally„Терминалът е изключен.“ Извикай го с „25“ и с „двайсет“. - Средна оценка без сривове. Прочети 5 реда с оценки. Пропусни тези, които не са цели числа (изключение) или не са от 2 до 6 (
if). Отпечатай средното с целочислено деление. Ако нито една оценка не е вярна, деленето на нула хвърля: хвани го и отпечатай „Няма верни оценки.“ - Предвиди и провери. Напиши
tryсexceptиfinally, в който всеки блок печата по ред. Предвиди изхода при успех, при хванато и при нехванато изключение и после го пусни.
Тест