Справочник по Python · модуль 8 · исключения и файлы
Глава 8.14
Команда assert:
проверки для себя
Модуль начинался с исключений, которые нужно перехватывать. Заканчивается он исключением, которое программист возбуждает сам — чтобы поймать собственную ошибку как можно раньше. assert проверяет предположение и обрывает работу, если оно не выполнилось.
- Категория
- Отладка
- Уровень
- Базовый
- Связанные темы
- Исключения, raise, тестирование
- Зачем это знать
- Находить ошибки в логике на раннем этапе
§1Зачем нужны утверждения
Программа пишется с предположениями: «здесь список уже отсортирован», «сюда приходит только положительное число», «на этом шаге словарь не пуст». Пока предположения верны, всё работает. Когда одно из них нарушается, ошибка редко проявляется сразу — она всплывает много позже и совсем в другом месте.
Команда assert предназначена для проверки условий во время выполнения. Если условие не выполняется, выбрасывается исключение AssertionError. Это удобный способ убедиться, что код работает так, как задумано, — особенно на этапе разработки и тестирования.
§2Синтаксис
Команда состоит из двух частей: условия, которое должно быть истинным, и необязательного сообщения об ошибке.
assert условие, сообщение_об_ошибке
§3Условие истинно
assert 2 + 2 == 4, "Математика сломалась!" print("программа продолжается")
программа продолжается
Если условие истинно, не происходит ничего: программа продолжает выполнение, как будто строки с assert и не было.
§4Условие ложно
assert 2 + 2 == 5, "Математика сломалась!" print("эта строка не выполнится")
AssertionError: Математика сломалась!
Программа прерывается на месте — там, где предположение оказалось неверным, а не там, где ошибка проявилась бы последствиями. В этом вся ценность: расстояние между причиной и симптомом сокращается до нуля.
§5Сообщение об ошибке
Сообщение необязательно, но без него в трассировке будет только имя исключения и строка кода.
assert x > 0 AssertionError
assert x > 0, "x должно AssertionError: x должно
быть больше нуля" быть больше нуля
Сообщение объясняет, что именно предполагалось. Писать его стоит всегда: через месяц условие assert len(rows) == len(headers) уже не расскажет, почему оно должно выполняться.
§6Сообщение как выражение
Сообщением может быть любое выражение, добавляющее контекст, — например, f-строка с текущим значением переменной.
x = -1 assert x > 0, f"Значение x ({x}) должно быть больше 0"
AssertionError: Значение x (-1) должно быть больше 0
Такое сообщение экономит отладку: не нужно добавлять print и запускать программу заново, чтобы узнать, чему равнялась переменная.
§7Любое логическое выражение
Условием может быть что угодно, что приводится к логическому типу: сравнение, вызов функции, проверка типа, составное выражение.
def process(data): assert isinstance(data, list), "Ожидался список" # продолжение обработки...
Здесь assert фиксирует договорённость: функция рассчитана на список. Если её однажды вызовут со строкой, ошибка проявится немедленно и с понятным текстом, а не через десять строк странным поведением.
§8Назначение — отладка
Основная цель assert — отладка программы. В ходе разработки часто нужно проверять корректность промежуточных данных или подтверждать выполнение ожидаемых условий; это помогает обнаружить и исправить ошибки на раннем этапе.
Но assert не должен заменять полноценную обработку ошибок или проверку данных, поступающих извне. Причина у этого ограничения не стилистическая, а вполне техническая — она в следующем параграфе.
§9Флаг -O выключает проверки
Когда Python запускается с флагом оптимизации -O, все утверждения assert игнорируются и не выполняются. Это немного повышает производительность, но может привести к пропуску ошибок в логике программы.
x = -10 assert x > 0, "x должно быть положительным" print("Программа продолжается")
| Запуск | Результат |
|---|---|
python script.py | AssertionError: x должно быть положительным |
python -O script.py | Программа продолжается |
С флагом -O программа продолжит выполнение даже при x = -10, и исключение выброшено не будет. Отсюда главное правило: программа не должна зависеть от того, выполнились assert или нет.
§10Не для внешних данных
Поскольку assert может быть отключён, его не следует использовать для проверки пользовательского ввода или данных из внешних источников — файлов, ответов сети, аргументов командной строки. Для таких задач применяют if с явной обработкой ошибок: это надёжнее.
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
assert | if + raise | |
|---|---|---|
| для чего | предположения автора кода | проверка входных данных |
| кому адресовано | программисту | пользователю и вызывающему коду |
при запуске с -O | исчезает | работает |
| тип исключения | всегда AssertionError | любой подходящий по смыслу |
| кто должен перехватывать | никто — это сигнал об ошибке в коде | вызывающий код |
Отдельная область, где assert уместен всегда, — автоматические тесты: там проверка и есть суть кода, а запуск с -O не используется.
§13Ловушка со скобками
Соблазн заключить условие и сообщение в скобки — как в вызове функции — приводит к молчаливой поломке.
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.pyif not ok: raise ValueError(...)Итог главы
assert проверяет предположение и возбуждает AssertionError, если оно нарушено. Это инструмент отладки и самотестирования: он ловит ошибку в момент возникновения, а не по её последствиям. Его не используют для проверки данных из внешних источников и не пишут в его условии ничего, что меняет состояние программы, — потому что запуск с флагом -O выключает все утверждения разом. Иными словами, assert адресован программисту, а if с raise — пользователю; путать эти два адресата не стоит.