Ошибки функций в Python: TypeError и его друзья
TypeError — короткое письмо о вызове: не хватило аргумента, пришёл лишний, не тот тип. Учимся читать трассировку и ловить ошибки честно.
Редакция Питоники
Всё, что вы знаете о функциях — аргументы из второго урока, умолчания из четвёртого, return из третьего, — живёт под одним законом: вызов должен совпасть с объявлением. Не совпал — Python не угадывает, а честно отказывается работать и объясняет отказ. TypeError — не приговор, а короткое письмо: имя функции, что не так и какой аргумент виноват. Читать такие сообщения — навык, а не проклятие. Разберём типичные вызовы-преступники на живых примерах.
Не хватает аргумента
Функция greet объявлена с обязательным аргументом name. Вызов без него — неполное письмо:
def greet(name):
return f'Привет, {name}!'
greet()
На странице падающие вызовы мы не запускаем — вместо этого ловим ошибку и печатаем её сообщение. Конструкция try/except говорит: попробуй выполнить, а если вылетит TypeError — обработай. Переменная e получает объект ошибки, и его печать даёт тот самый текст:
def greet(name):
return f'Привет, {name}!'
try:
greet()
except TypeError as e:
print('Поймали:', e)
print(greet('Анна'))
Поймали: greet() missing 1 required positional argument: 'name' Привет, Анна!
Правильный вызов после ошибки отработал как ни в чём не бывало: перехват спасает программу, а не чинит функцию. Лечится нехватка тремя способами — передать аргумент, задать умолчание в объявлении или разрешить ноль аргументов через *args, если он там уместен.
Лишний аргумент
Обратная ситуация: функция ждёт два числа, а ей принесли три. Лишний аргумент не отбрасывается молча — вызов падает:
def area(width, height):
return width * height
area(3, 4, 5)
def area(width, height):
return width * height
try:
area(3, 4, 5)
except TypeError as e:
print('Поймали:', e)
print(area(3, 4))
Поймали: area() takes 2 positional arguments but 3 were given 12
Сообщение честное до деталей: две — это width и height, третья величина аргументом не предусмотрена. Если лишние аргументы в функции законны, её объявляют с *args из урока про *args — тогда падения не будет.
Не тот тип
Число аргументов совпало, но тип — нет. Функция складывает число с единицей, а ей пришла строка:
def add_one(n):
return n + 1
add_one('5')
def add_one(n):
return n + 1
try:
add_one('5')
except TypeError as e:
print('Поймали:', e)
print(add_one(5))
print(add_one(int('5')))
Поймали: can only concatenate str (not "int") to str 6 6
Последняя строка — лечение: int('5') превратил строку в число до вызова. Аннотации из урока про аннотации такие вещи не ловят — преобразование всегда на совести кода.
TypeError и ValueError: не тот тип и не то значение
У TypeError есть частый напарник — ValueError. Разница в вопросе: TypeError говорит «операция с таким типом невозможна», ValueError — «тип годится, значение нет». Классика: int('сорок два') — строку в число превращать умеем, но в этой строке не число:
def safe_div(a, b):
try:
return a / b
except ZeroDivisionError:
return 'делить на ноль нельзя'
def safe_int(text):
try:
return int(text)
except ValueError:
return None
print(safe_div(10, 2))
print(safe_div(10, 0))
print(safe_int('42'))
print(safe_int('сорок два'))
5.0 делить на ноль нельзя 42 None
Каждая функция поймала свою ошибку и вернула запасной ответ: деление на ноль — вежливая строка, негодная строка — None. Тип ошибки в except стоит точный: ловить голое except без имени и вида — значит глотать и опечатки, и настоящие баги.
Как читать трассировку
Когда ошибку никто не поймал, Python печатает трассировку — traceback: маршрут от точки запуска до места катастрофы. Строка Traceback (most recent call last) — начало маршрута, дальше — файлы, номера строк и сами строки кода по пути, а самая последняя строка — тип и сообщение ошибки. Правило чтения — снизу вверх:
- последняя строка — диагноз: вид ошибки и её сообщение, с этого начинайте всегда;
- строки над ней — путь: где случился удар, какая строка кода вызвала падение;
- если в маршруте несколько файлов — ваши строки там, где ваш файл, чужие библиотеки листайте мимо;
- числа слева — номера строк: открывайте файл на нужной строке, а не листайте наугад.
def order(total, discount):
return total - discount
order(100, 'пять')
Именованные аргументы против путаницы
Часть ошибок вызова лечится ещё на месте вызова. Именованные аргументы делают вызов самодокументированным и не зависят от порядка:
def order(total, discount):
return total - discount
try:
order(100, 20, 5)
except TypeError as e:
print('Поймали:', e)
print(order(100, 20))
print(order(total=100, discount=20))
Поймали: order() takes 2 positional arguments but 3 were given 80 80
А вот перестановка аргументов местами — ошибка, которая не падает. Обе величины числа, вызов проходит, но ответ абсурден:
def order(total, discount):
return total - discount
print(order(100, 20))
print(order(20, 100))
80 -80
None из функции без return
Отдельный сорт TypeError приходит с задержкой. Функция печатает, но не возвращает — и её результат, None, уезжает дальше по программе, где ломается на первом же методе:
def save(text):
print('сохранили', text)
result = save('отчёт')
print(result)
try:
result.upper()
except AttributeError as e:
print('Поймали:', e)
сохранили отчёт None Поймали: 'NoneType' object has no attribute 'upper'
Сообщение называется AttributeError, но корень — вызов функции, которая ничего не вернула: у None нет методов. Правило из урока про return снова спасает: если результат функции нужен дальше — функция обязана его возвращать, а не печатать.
Полный список друзей
| Ошибка | Когда возникает | Пример |
|---|---|---|
| TypeError | Не тот тип или число аргументов | area(3, 4, 5) при двух параметрах |
| ValueError | Тип тот, значение неподходящее | int('сорок два') |
| ZeroDivisionError | Деление на ноль | a / b при b равном нулю |
| AttributeError | Метода у значения нет | None.upper() |
| NameError | Имя не объявлено | вызов функции с опечаткой в имени |
- чтение сообщений: missing 1 required positional argument — не приговор, а указание на недостающий аргумент;
- try/except с конкретным типом — для вызовов с непредсказуемыми данными, например из пользовательского ввода;
- именование аргументов при вызове — страховка от перепутанного порядка;
- трассировка читается снизу вверх — от диагноза к месту вызова.
Ошибки перестали быть стеной: каждое сообщение — короткое письмо с именем функции и сутью отказа, а трассировка — карта пути к нему. Курс подходит к финалу: в последнем уроке соберём собственную библиотеку утилит — с docstring, умолчаниями и мини-проверками, где ошибки обрабатываются, а не игнорируются.
Своя проверка вместо падения
Поймать ошибку — полдела. Лучше не доводить до неё: функция может сама проверить входные данные и сказать, что не так. Для этого есть оператор raise — он поднимает ошибку с вашим текстом.
def average(numbers):
if not numbers:
raise ValueError('список пуст')
return sum(numbers) / len(numbers)
try:
print(average([]))
except ValueError as e:
print('Поймали:', e)
print(average([2, 4, 6]))
Поймали: список пуст 4.0
Вместо стандартного division by zero вызывающий получает внятное «список пуст». Имя ошибки выбрано не случайно: тип подходит — числа, значение неподходящее — пустой список, — значит, это ValueError, а не TypeError. Выбор класса ошибки сам по себе сообщает читателю кода, что именно сломалось.
Свои исключения
Когда стандартных видов не хватает, ошибку можно завести свою: обычный класс, наследник Exception. Так вы отделяете свои проблемы от чужих и ловите их точечно.
class OrderError(Exception):
"""Ошибка заказа: сумма или количество не подходят."""
def place_order(total, quantity):
if quantity <= 0:
raise OrderError('количество должно быть положительным')
if total < 0:
raise OrderError('сумма не может быть отрицательной')
return f'заказ на {total} руб., {quantity} шт.'
try:
print(place_order(500, 0))
except OrderError as e:
print('Поймали:', e)
print(place_order(500, 2))
Поймали: количество должно быть положительным заказ на 500 руб., 2 шт.
except OrderError поймает только нашу ошибку и не заденет ничьего: чужой TypeError внутри функции пролетит мимо, и о нём вы узнаете сразу. Наследоваться можно и от конкретного вида — например, от ValueError, — если хотите, чтобы вашу ошибку ловили общие обработчики ValueError.
finally: действие в любом случае
У конструкции try/except есть третья часть — finally. Она выполняется всегда: и когда try отработал без ошибки, и когда ошибка ушла в except. В ней живут действия, которые обязаны произойти в любом случае: закрыть файл, сбросить флаг, записать в журнал.
def read_first(lines):
try:
return lines[0]
finally:
print('проверка завершена')
print(read_first(['первая', 'вторая']))
проверка завершена первая
Обратите внимание на порядок строк: сначала выполнился finally, и только потом вызывающий получил значение. Возвращаемое значение вычисляется заранее, но программа добегает до конца блока try, прежде чем отдать его наружу. Если внутри try случится ошибка, finally всё равно отработает — это и есть его главное свойство.
Практика: безопасное деление
Соберём вместе проверку, raise и try/except — так выглядит функция, которой не страшно отдать плохие данные.
def share_of_total(part, total):
if total <= 0:
raise ValueError('итог должен быть положительным')
if part < 0:
raise ValueError('часть не может быть отрицательной')
return round(part * 100 / total, 1)
for args in [(30, 120), (30, 0)]:
try:
print(share_of_total(*args))
except ValueError as e:
print('Поймали:', e)
25.0 Поймали: итог должен быть положительным
Первый вызов посчитал долю, второй остановился на собственной проверке, и вызывающий получил внятный текст вместо division by zero. Функция проверяет вход и честно отказывается работать с бессмыслицей — а вызывающий решает, что с этим делать: попробовать другие данные или сообщить пользователю.
Сначала предскажи ответ в голове — это главный навык программиста.
def safe_div(a, b):
try:
return a / b
except ZeroDivisionError:
return 'делить на ноль нельзя'
print(safe_div(10, 0))
def order(total, discount):
return total - discount
print(order(20, 100))
def safe_int(text):
try:
return int(text)
except ValueError:
return None
print(safe_int('42'))
print(safe_int('сорок два'))
1. Что означает сообщение greet() missing 1 required positional argument: 'name'?
2. int('сорок два') падает с какой ошибкой?
3. С какой строки начинают читать трассировку?
4. Что получает переменная e в записи except TypeError as e?
5. order(total=100, discount=20) против order(100, 20) — в чём выгода именованной формы?
6. Функция печатает результат, но не возвращает его. Что прилетит туда, где ждут результат?
7. Что произойдёт при вызове функции, которая проверяет аргументы и поднимает свою ошибку?
8. Когда выполняется блок finally?
9. Почему лучше raise ValueError('список пуст'), чем делить на ноль?
Оберни вызов average на пустом списке в try/except: пусть программа напечатает среднее для данных, а на пустом списке — вежливую строку вместо падения.
Что значит ошибка TypeError: missing 1 required positional argument?
Вызов не передал обязательный аргумент: сообщение называет функцию и имя недостающего. Лечится передачей аргумента или значением по умолчанию в объявлении: def greet(name='друг').
Урок 4: умолчания и именованные аргументыЧем TypeError отличается от ValueError?
TypeError — операция невозможна с таким типом: строку нельзя вычесть из числа. ValueError — тип годится, значение нет: int умеет превращать строки в числа, но строка «сорок два» числа не содержит.
Как понять, где произошла ошибка, по трассировке?
Читают снизу вверх: последняя строка — вид ошибки и сообщение, строки выше — файлы, номера строк и код по пути вызова. Ваша строка — та, где ваш файл; последняя строка маршрута обычно и есть место удара.
Функция напечатала результат, но дальше всё сломалось с AttributeError. Почему?
Функция печатает, но не возвращает: её результат — None, а у None нет методов. Если результат нужен дальше по программе, функция обязана возвращать его через return, а печатать должен вызывающий код.
Урок 3: returnКак завести свою ошибку в Python?
Обычный класс-наследник Exception: class OrderError(Exception):. Поднимайте его через raise OrderError('текст') и ловите точечно — except OrderError поймает только вашу ошибку, а чужая TypeError пролетит мимо.
Когда нужен try/except, а когда достаточно проверки if?
try/except — там, где ошибку нельзя предвидеть: данные из сети, битый файл, ввод пользователя. Если условие известно заранее, проверьте его обычным if и поднимите свою ошибку через raise с понятным текстом.
Что такое блок finally?
Третья часть конструкции try/except/finally: он выполняется всегда, и когда try отработал успешно, и когда ошибка ушла в except. В нём закрывают файлы, сбрасывают флаги и пишут в журнал — всё, что обязано произойти при любом исходе.
Почему функция должна проверять аргументы сама, а не полагаться на вызывающего?
Потому что вызывающих может быть много, и проверка в одном месте не забудется. Функция, которая принимает решения о своих входных данных, работает одинаково при любом вызове — а проверка if в вызывающем коде живёт только там, где её написали.
Понравился урок? Сошлитесь на него
«Разница в вопросе: TypeError говорит «операция с таким типом невозможна», ValueError — «тип годится, значение нет».»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Функции · Урок 2
Аргументы функции в Python: данные внутрь
Функция принимает данные через параметры: greet('Анна') здоровается с Анной. Разбираем позиционные аргументы, порядок и количество — и типичную ошибку TypeError.
Функции · Урок 4
Умолчания и именованные аргументы в Python
Необязательные параметры с запасными значениями и вызов по имени: def f(x, n=2) и f(x=5, n=3). Разбираем порядок параметров и знакомимся с ловушкой изменяемого умолчания.
Функции · Урок 3
Return в Python: функция возвращает результат
print показывает значение человеку, return отдаёт программе. Учимся возвращать результат, класть его в переменную и использовать в выражениях — и не путать return с print.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Исключения · Урок 1
Что такое исключение: первая ошибка в программе
Первая ошибка в программе: читаем traceback за десять секунд — тип ошибки, место и сообщение. Разбираем четыре типовых падения на живом коде.
traceback pythonошибки python для начинающих
Исключения · Урок 3
except ValueError: ловим конкретный тип ошибки
Уточняем обработчик: except ValueError ловит только свой тип ошибок. Почему голый except прячет опечатки и как ValueError ведёт себя у int и list.remove.
except valueerror pythonpython valueerror
Функции · Урок 13
Функция как значение в Python: передача функций
map и sorted уже принимали функции в аргументах — теперь разберём приём целиком: функция такое же значение, как число или строка.
python функция как аргументфункция как значение python