Към съдържанието
wannacode

Когато нещо се обърка

Проблем

12а вместо 12

Касата за концерта пита колко билета искаш. Билетът е 15 €.

tickets.py
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-а. На кой ред от програмата е грешката?

Избери реда с грешката.

Идеята

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("Благодарим ти!")
Стъпка по стъпка, с думи
  1. Програмата пита колко билета. Въвеждаш „12а“.
  2. В try: int() опитва да направи число от „12а“.
  3. Не може. Функцията хвърля ValueError.
  4. Изпълнението скача направо в except. Редът с „Общо“ се прескача.
  5. except е за точно този вид грешка: печата съобщението за клиента.
  6. След 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("Край")
Отговор

Идеята

Какво пише в изключението

Изключението е обект, а 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())
Изход
True
False
False

Пробвай

Деление на нула

Не всяко изключение идва от входа. Средната стойност на поръчка е приходът, делен на броя поръчки. Но в първия ден поръчки няма. С целочислено деление revenue // orders спира програмата:

Грешка
ZeroDivisionError: integer division or modulo by zero

А с обикновено деление /, което дава дробно число?

Какво ще отпечата програмата?

revenue = 90
orders = 0
average = revenue / orders
print(average)
Отговор
По-точно, за любопитните
  • Дробните числа на Python следват стандарта IEEE 754 и имат безкрайност и nan, но деленето на нула не ги дава: Python избира да хвърли. Безкрайност получаваш само ако я поискаш:
print(float("inf"))
print(float("inf") - float("inf"))
Изход
inf
nan

Упражнение

Програми, които не спират

Реши задачите в редактора си. Не печатай въпроси, за да съвпадне изходът ти с примерния.

1. Текст към число. Напиши функция to_number(text), която връща текста като float. Прочети два реда. За всеки отпечатай числото или съобщение, че не е число. Хвани изключението там, където викаш функцията.

Вход
123.45
123.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 €.

Вход
90
0
Изход
Още няма поръчки.

Още задачи

  • Възраст, докато е вярна. Питай за възраст, докато входът не е цяло число от 0 до 120. За текст като „двайсет“ кажи „Това не е число.“, а за 150 кажи „Възрастта е от 0 до 120.“ Едното е изключение, другото е if.
  • Сума на верните. Прочети 5 реда. Събери тези, които са цели числа, и преброй пропуснатите: при вход 10, abc, 5, 7.5, 3 изходът е Сума: 18, пропуснати: 2.
  • Деление по желание. Прочети две цели числа и отпечатай частното. При текст вместо число и при деление на нула програмата не спира, а пита отново за двете числа.

Тест

Провери се

1. Какво е изключение?
2. Програмата спря с Traceback. Откъде да започнеш да четеш?
3. В try има четири реда. Вторият хвърля изключение, а except го хваща. Какво става с третия и четвъртия?
4. Какво става при average = revenue / orders, когато orders е 0?
5. except ValueError: хваща ли грешката от scores[10] в списък с 5 елемента?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·