Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 19 из 20 Начальный 20 мин 100 XP

Ошибки функций в Python: TypeError и его друзья

TypeError — короткое письмо о вызове: не хватило аргумента, пришёл лишний, не тот тип. Учимся читать трассировку и ловить ошибки честно.

Редакция Питоники

Всё, что вы знаете о функциях — аргументы из второго урока, умолчания из четвёртого, return из третьего, — живёт под одним законом: вызов должен совпасть с объявлением. Не совпал — Python не угадывает, а честно отказывается работать и объясняет отказ. TypeError — не приговор, а короткое письмо: имя функции, что не так и какой аргумент виноват. Читать такие сообщения — навык, а не проклятие. Разберём типичные вызовы-преступники на живых примерах.

Не хватает аргумента

Функция greet объявлена с обязательным аргументом name. Вызов без него — неполное письмо:

ошибка: вызов без аргумента
def greet(name):
    return f'Привет, {name}!'

greet()
В терминале Python ответит: TypeError: greet() missing 1 required positional argument: 'name'. Сообщение называет функцию, причину и имя виноватого аргумента — не хватает одного обязательного позиционного аргумента.

На странице падающие вызовы мы не запускаем — вместо этого ловим ошибку и печатаем её сообщение. Конструкция 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)
Python ответит: TypeError: area() takes 2 positional arguments but 3 were given. Читается дословно: функция принимает 2 позиционных аргумента, а передано 3.
ловим лишний аргумент
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')
В терминале Python ответит: TypeError: can only concatenate str (not "int") to str. Перевод: строку можно склеивать только со строкой, а ей попытались прибавить число.
ловим чужой тип и чиним
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, 'пять')
В терминале появится трассировка: строка Traceback (most recent call last):, затем строка вызова order(100, 'пять'), затем строка тела функции и последняя строка — TypeError: unsupported operand type(s) for -: 'int' and 'str'. Читаем снизу вверх: из числа вычитали строку, удар пришёлся на строку return внутри order.

Именованные аргументы против путаницы

Часть ошибок вызова лечится ещё на месте вызова. Именованные аргументы делают вызов самодокументированным и не зависят от порядка:

именованный вызов как страховка
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, уезжает дальше по программе, где ломается на первом же методе:

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. В ней живут действия, которые обязаны произойти в любом случае: закрыть файл, сбросить флаг, записать в журнал.

finally выполняется всегда
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('сорок два'))
Проверь себя
0 / 9

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('список пуст'), чем делить на ноль?

Карточки терминов
Запомнено: 0 / 8
Практика

Оберни вызов average на пустом списке в try/except: пусть программа напечатает среднее для данных, а на пустом списке — вежливую строку вместо падения.

practice.py
Вопросы и ответы по уроку

Что значит ошибка 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.