Модуль 8 · Исключения и файлы

Справочник по Python · модуль 8 · исключения и файлы

Глава 8.14

8.14

Команда assert:
проверки для себя

Модуль начинался с исключений, которые нужно перехватывать. Заканчивается он исключением, которое программист возбуждает сам — чтобы поймать собственную ошибку как можно раньше. assert проверяет предположение и обрывает работу, если оно не выполнилось.

Категория
Отладка
Уровень
Базовый
Связанные темы
Исключения, raise, тестирование
Зачем это знать
Находить ошибки в логике на раннем этапе

§1Зачем нужны утверждения

Программа пишется с предположениями: «здесь список уже отсортирован», «сюда приходит только положительное число», «на этом шаге словарь не пуст». Пока предположения верны, всё работает. Когда одно из них нарушается, ошибка редко проявляется сразу — она всплывает много позже и совсем в другом месте.

Команда assert предназначена для проверки условий во время выполнения. Если условие не выполняется, выбрасывается исключение AssertionError. Это удобный способ убедиться, что код работает так, как задумано, — особенно на этапе разработки и тестирования.

§2Синтаксис

Команда состоит из двух частей: условия, которое должно быть истинным, и необязательного сообщения об ошибке.

Синтаксисвторая часть необязательна
assert условие, сообщение_об_ошибке

§3Условие истинно

main.pyпроверка пройдена
assert 2 + 2 == 4, "Математика сломалась!"
print("программа продолжается")
Выводassert не оставил следов
программа продолжается

Если условие истинно, не происходит ничего: программа продолжает выполнение, как будто строки с assert и не было.

§4Условие ложно

main.pyпредположение нарушено
assert 2 + 2 == 5, "Математика сломалась!"
print("эта строка не выполнится")
Ошибкаработа прервана
AssertionError: Математика сломалась!

Программа прерывается на месте — там, где предположение оказалось неверным, а не там, где ошибка проявилась бы последствиями. В этом вся ценность: расстояние между причиной и симптомом сокращается до нуля.

§5Сообщение об ошибке

Сообщение необязательно, но без него в трассировке будет только имя исключения и строка кода.

Сравнениечто увидит тот, кто читает ошибку
assert x > 0                       AssertionError

assert x > 0, "x должно            AssertionError: x должно
              быть больше нуля"    быть больше нуля

Сообщение объясняет, что именно предполагалось. Писать его стоит всегда: через месяц условие assert len(rows) == len(headers) уже не расскажет, почему оно должно выполняться.

§6Сообщение как выражение

Сообщением может быть любое выражение, добавляющее контекст, — например, f-строка с текущим значением переменной.

main.pyзначение прямо в тексте ошибки
x = -1
assert x > 0, f"Значение x ({x}) должно быть больше 0"
Ошибкавидно не только правило, но и факт
AssertionError: Значение x (-1) должно быть больше 0

Такое сообщение экономит отладку: не нужно добавлять print и запускать программу заново, чтобы узнать, чему равнялась переменная.

§7Любое логическое выражение

Условием может быть что угодно, что приводится к логическому типу: сравнение, вызов функции, проверка типа, составное выражение.

main.pyпроверка типа данных
def process(data):
    assert isinstance(data, list), "Ожидался список"
    # продолжение обработки...

Здесь assert фиксирует договорённость: функция рассчитана на список. Если её однажды вызовут со строкой, ошибка проявится немедленно и с понятным текстом, а не через десять строк странным поведением.

§8Назначение — отладка

Основная цель assert — отладка программы. В ходе разработки часто нужно проверять корректность промежуточных данных или подтверждать выполнение ожидаемых условий; это помогает обнаружить и исправить ошибки на раннем этапе.

Но assert не должен заменять полноценную обработку ошибок или проверку данных, поступающих извне. Причина у этого ограничения не стилистическая, а вполне техническая — она в следующем параграфе.

§9Флаг -O выключает проверки

Когда Python запускается с флагом оптимизации -O, все утверждения assert игнорируются и не выполняются. Это немного повышает производительность, но может привести к пропуску ошибок в логике программы.

script.pyпроверка, которая может не сработать
x = -10
assert x > 0, "x должно быть положительным"
print("Программа продолжается")
ЗапускРезультат
python script.pyAssertionError: x должно быть положительным
python -O script.pyПрограмма продолжается

С флагом -O программа продолжит выполнение даже при x = -10, и исключение выброшено не будет. Отсюда главное правило: программа не должна зависеть от того, выполнились assert или нет.

§10Не для внешних данных

Поскольку assert может быть отключён, его не следует использовать для проверки пользовательского ввода или данных из внешних источников — файлов, ответов сети, аргументов командной строки. Для таких задач применяют if с явной обработкой ошибок: это надёжнее.

Неправильнопроверка исчезнет при запуске с -O
def read_file(filename):
    assert filename.endswith('.txt'), "Ожидался текстовый файл"
    with open(filename) as f:
        return f.read()
Правильнопроверка есть при любом запуске
def read_file(filename):
    if not filename.endswith('.txt'):
        raise ValueError("Ожидался текстовый файл")
    with open(filename) as f:
        return f.read()

У второго варианта есть и смысловое преимущество: ValueError из главы 8.2 точно называет проблему — значение неподходящее. AssertionError сообщает лишь, что нарушилось какое-то предположение автора кода.

§11Условие не должно менять состояние

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

Плохопроверка меняет список
assert my_list.pop() == 5, "Последний элемент должен быть 5"
Хорошоизменение отделено от проверки
last_element = my_list.pop()
assert last_element == 5, "Последний элемент должен быть 5"

Если assert отключён, метод pop() в первом варианте не вызовется — и список останется с лишним элементом. Программа с -O начнёт вести себя иначе, чем без него, а найти такую разницу крайне тяжело.

§12assert против if и raise

assertif + raise
для чегопредположения автора кодапроверка входных данных
кому адресованопрограммиступользователю и вызывающему коду
при запуске с -Oисчезаетработает
тип исключениявсегда AssertionErrorлюбой подходящий по смыслу
кто должен перехватыватьникто — это сигнал об ошибке в кодевызывающий код

Отдельная область, где assert уместен всегда, — автоматические тесты: там проверка и есть суть кода, а запуск с -O не используется.

§13Ловушка со скобками

Соблазн заключить условие и сообщение в скобки — как в вызове функции — приводит к молчаливой поломке.

main.pyлишние скобки
assert (1 == 2, "это сообщение не сработает")
print("программа продолжает работу")
Выводпроверка не сработала
программа продолжает работу

В скобках оказался кортеж из двух элементов, а непустой кортеж всегда истинен — условие выполняется при любых данных. Python предупредит об этом сообщением SyntaxWarning: assertion is always true, perhaps remove parentheses?, но программа продолжит работать, и проверка окажется декорацией. assert — команда, а не функция: скобки после неё не нужны.

§14Частые ошибки

Что написаноЧто произойдётКак правильно
assert (cond, "текст")кортеж всегда истинен, проверка не работаетassert cond, "текст"
assert для проверки ввода пользователяс флагом -O проверка исчезнетif и raise ValueError
assert my_list.pop() == 5с -O список не изменится, логика сломаетсявынести изменение отдельной строкой
except AssertionError в рабочем кодеошибка в логике будет скрытаисправить причину, а не перехватывать
assert без сообщенияв трассировке не видно, что предполагалосьдобавить пояснение
рассчитывать на assert в готовой программепроверки могут быть отключеныкритичные проверки писать через if

§15Проверьте себя

Что делает assert, если условие истинно?

Ничего: программа продолжает выполнение, как если бы строки не было.

Какое исключение возбуждается при ложном условии?

AssertionError — с сообщением, если оно указано.

Что происходит при запуске с флагом -O?

Все assert игнорируются и не выполняются.

Почему assert не годится для проверки пользовательского ввода?

Потому что его можно отключить. Проверка внешних данных должна работать всегда, поэтому пишется через if с raise.

Чем плох assert my_list.pop() == 5?

Условие изменяет состояние программы. При отключённых проверках pop() не вызовется, и поведение программы станет другим.

Почему assert (cond, "текст") не работает?

В скобках получается кортеж, а непустой кортеж всегда истинен, поэтому проверка проходит при любых данных.

§16Шпаргалка

Проверкаassert условие, "сообщение"
С контекстомassert x > 0, f"x = {x}"
Проверка типаassert isinstance(data, list)
Отключитьpython -O script.py
Для внешних данныхif not ok: raise ValueError(...)

Итог главы

assert проверяет предположение и возбуждает AssertionError, если оно нарушено. Это инструмент отладки и самотестирования: он ловит ошибку в момент возникновения, а не по её последствиям. Его не используют для проверки данных из внешних источников и не пишут в его условии ничего, что меняет состояние программы, — потому что запуск с флагом -O выключает все утверждения разом. Иными словами, assert адресован программисту, а if с raise — пользователю; путать эти два адресата не стоит.

Глава 8.14 · Команда assert · Справочник по Python Автор: Макаров Максим Николаевич