Когато нещо се обърка
Проблем
12а вместо 12
Касата за концерта пита колко билета искаш. Билетът е 15 €.
tickets = int(input("Колко билета? "))print(f"Общо: {tickets * 15} €")С вход 12 програмата казва „Общо: 180 €“. Но клиентът бърза и натиска една буква повече:
Колко билета? 12аTraceback (most recent call last): File "/projects/tickets/tickets.py", line 1, in <module> tickets = int(input("Колко билета? ")) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ValueError: invalid literal for int() with base 10: '12а'Програмата спира. Вместо сумата излиза дълъг текст, а редът „Общо“ изобщо не се стига. Какво значи този текст и как да не спира програмата заради една буква?
Идеята
Изключение
int() трябва да върне число. От „12а“ не може да направи число, а да върне каквото и да е число би било лъжа. Затова функцията създава обект, който описва проблема, и го хвърля. Този обект се казва изключение (exception).
На английски в Python се казва raise („вдигни“), но смисълът е същият. Ако никой не го хване, програмата спира и показва какво е станало. Този текст се казва traceback и се чете отдолу нагоре, защото най-новото е най-долу (most recent call last):
- Последният ред: видът на изключението и съобщението.
ValueError: invalid literal for int() with base 10: '12а': грешна стойност, ето кой текст. - Редовете с „File“ над него: къде е станало. Това е стекът от извиквания от урока за рекурсията, но отвън навътре: най-горе е най-външното място, а най-долу е функцията, която е хвърлила.
<module>значи кодът на най-горно ниво във файла. - Твоят ред: последният ред „File“, който сочи твоя файл. Тук:
tickets.py,line 1. Под него е самият ред, а знаците^^^показват частта, която е хвърлила.
Съобщенията са на английски, защото ги пише езикът, не ти.
Пробвай
Намери грешката
Програмата трябва да отпечата последния резултат от контролното. Тя спира с това:
Traceback (most recent call last): File "/projects/tickets/scores.py", line 6, in <module> print(last_score(scores)) ^^^^^^^^^^^^^^^^^^ File "/projects/tickets/scores.py", line 2, in last_score return scores[len(scores)] ~~~~~~^^^^^^^^^^^^^IndexError: list index out of rangeПрочети traceback-а. На кой ред от програмата е грешката?
Избери реда с грешката.
last_score, ред 2. Редът над него е ред 6: оттам е извикана функцията, но грешката не е там. Последният ред казва и каква е тя: индекс извън списъка. При 5 елемента последният индекс е len(scores) - 1. В Python може и по-кратко: scores[-1] е последният елемент.Идеята
try и except
Изключението може да се хване. Кодът, който може да хвърли, отива в try („опитай“). Какво да стане при грешка, пишеш в except („освен“, т.е. „освен ако не стане това“):
text = input("Колко билета? ")try: tickets = int(text) print(f"Общо: {tickets * 15} €")except ValueError: print("Това не е цяло число.")print("Благодарим ти!")12аКолко билета? Това не е цяло число.Благодарим ти!- След
exceptпишеш кой вид изключение хващаш, тукValueError. - Ако в
tryвсичко мине,exceptсе прескача. С вход12изходът е „Общо: 180 €“ и „Благодарим ти!“. - След
exceptпрограмата продължава нататък. Тя вече не спира.
text = input("Колко билета? ")try: tickets = int(text) print(f"Общо: {tickets * 15} €")except ValueError: print("Това не е цяло число.")print("Благодарим ти!")Стъпка по стъпка, с думи
- Програмата пита колко билета. Въвеждаш „12а“.
- В try: int() опитва да направи число от „12а“.
- Не може. Функцията хвърля ValueError.
- Изпълнението скача направо в except. Редът с „Общо“ се прескача.
- except е за точно този вид грешка: печата съобщението за клиента.
- След except програмата продължава. Не спира.
По-точно, за любопитните
- В Python променлива, създадена в
try, съществува и след него: блоковете нямат своя област. Но акоint()е хвърлил,ticketsизобщо не е създадена, иprint(tickets)след блока ще спре сNameError.
try: tickets = int("12")except ValueError: print("Това не е цяло число.")print(tickets)12- Името на изключението не е задължително:
except ValueError:. Ако ти трябва, пишешexcept ValueError as e:.
Пробвай
Кое се изпълнява?
Какво ще отпечата програмата?
print("Начало")try: print("Чета") seats = int("десет") print(f"Прочетох {seats}")except ValueError: print("Грешен вход")print("Край")„Чета“ се отпечатва, защото е преди грешката. После int() хвърля и изпълнението скача направо в except. „Прочетох“ се пропуска. След except програмата продължава с „Край“.
Идеята
Какво пише в изключението
Изключението е обект, а e в except ValueError as e: е променливата, която сочи към него. Съобщението е самият текст на обекта: str(e), или направо {e} във f-низ.
try: tickets = int(input()) print(f"Общо: {tickets * 15} €")except ValueError as e: print(f"Грешка: {e}")12аГрешка: invalid literal for int() with base 10: '12а'Съобщението е полезно за теб, когато търсиш грешката. На клиента обикновено показваш свой текст на неговия език, като „Това не е цяло число.“
Идеята
Питай, докато е вярно
Клиентът сбърка. Не е нужно да спираш. Питай отново:
while True: try: tickets = int(input("Колко билета? ")) break except ValueError: print("Въведи цяло число, например 3.")print(f"Общо: {tickets * 15} €")12адванайсет12Колко билета? Въведи цяло число, например 3.Колко билета? Въведи цяло число, например 3.Колко билета? Общо: 180 €while True се върти, докато break не го прекъсне. А break се стига само ако int() е успяла. При грешка изпълнението скача в except, след него цикълът започва отначало и пита пак.
По-точно, за любопитните
- В Python няма
TryParse. Хващането наValueErrorе обичайният начин: „опитай и хвани“ е навик в Python. Имаtext.isdigit(), но тя казваFalseи за"-3", затова не заместваint():
print("12".isdigit())print("12а".isdigit())print("-3".isdigit())TrueFalseFalseПробвай
Деление на нула
Не всяко изключение идва от входа. Средната стойност на поръчка е приходът, делен на броя поръчки. Но в първия ден поръчки няма. С целочислено деление revenue // orders спира програмата:
ZeroDivisionError: integer division or modulo by zeroА с обикновено деление /, което дава дробно число?
Какво ще отпечата програмата?
revenue = 90orders = 0average = revenue / ordersprint(average)ZeroDivisionError винаги: и с //, и с /, и при дробни числа. Програмата спира, вместо да продължи с грешна стойност. Ако поръчки може да няма, провери делителя с if или хвани ZeroDivisionError.По-точно, за любопитните
- Дробните числа на Python следват стандарта IEEE 754 и имат безкрайност и
nan, но деленето на нула не ги дава: Python избира да хвърли. Безкрайност получаваш само ако я поискаш:
print(float("inf"))print(float("inf") - float("inf"))infnanУпражнение
Програми, които не спират
Реши задачите в редактора си. Не печатай въпроси, за да съвпадне изходът ти с примерния.
1. Текст към число. Напиши функция to_number(text), която връща текста като float. Прочети два реда. За всеки отпечатай числото или съобщение, че не е число. Хвани изключението там, където викаш функцията.
123.45123.abcЧислото е: 123.45Грешка: 123.abc не е число.2. Няма такъв продукт. Цените са в списък [120, 75, 300, 45, 89]. Прочети номер на продукт от 1 до 5 и отпечатай цената му. Ако номерът е извън списъка, хвани изключението и го кажи. С вход 2 изходът е Цена: 75 €.
7Няма продукт с номер 7.Внимавай с номер 0: prices[-1] в Python не хвърля, а е последната цена, 89 €. Отрицателният индекс брои от края. Затова за 0 добави и if.
3. Средна поръчка. Прочети прихода и броя поръчки (цели числа) и отпечатай средното с целочислено деление. Ако поръчки няма, хвани изключението. С вход 900 и 4 изходът е Средно: 225 €.
900Още няма поръчки.Още задачи
- Възраст, докато е вярна. Питай за възраст, докато входът не е цяло число от 0 до 120. За текст като „двайсет“ кажи „Това не е число.“, а за 150 кажи „Възрастта е от 0 до 120.“ Едното е изключение, другото е
if. - Сума на верните. Прочети 5 реда. Събери тези, които са цели числа, и преброй пропуснатите: при вход
10,abc,5,7.5,3изходът еСума: 18, пропуснати: 2. - Деление по желание. Прочети две цели числа и отпечатай частното. При текст вместо число и при деление на нула програмата не спира, а пита отново за двете числа.
Тест