assert в Python
Инструкция assert
применяется для автоматического обнаружения ошибок в программах Python. Эта инструкция сделает ваши программы надежнее и проще в отладке.
По своей сути инструкция assert
представляет собой средство отладки, которое проверяет условие. Если условие утверждения assert
истинно, то ничего не происходит и ваша программа продолжает выполняться как обычно. Но если же вычисление условия дает результат ложно, то вызывается исключение AssertionError
с необязательным сообщением об ошибке.
Пример использования assert
Предположим, вы создаете интернет-магазин с помощью Python. Вы работаете над добавлением в систему функциональности скидочного купона, и в итоге вы пишете следующую функцию apply_discount()
:
def apply_discount(product, discount):
price = int(product['цена'] * (1.0 — discount))
assert 0 <= price <= product['цена']
return price
2
3
4
Инструкция assert
будет гарантировать, что, независимо от обстоятельств, вычисляемые этой функцией сниженные цена не может быть ниже 0 и выше первоначальной цены товара.
Давайте убедимся, что эта функция действительно работает как задумано, если вызвать ее, применив допустимую скидку. В этом примере товары в нашем магазине будут представлены в виде простых словарей, для демонстрации утверждений assert
:
shoes = {'name': 'shoes', 'price': 14999}
Избегая проблем с округлением денежной цены, используйте целое число для представления цены в копейках. Итак, если к этим туфлям мы применим 25 %-ную скидку, то ожидаемо придем к отпускной цене 112,49:
apply_discount(shoes, 0.25) # 112,49
Функция сработала. Теперь попробуем применить несколько недопустимых скидок. Например, 200%-ную «скидку», которая вынудит нас отдать деньги покупателю:
>>> apply_discount(shoes, 2.0)
Traceback (most recent call last):
File "<input>", line 1, in <module>
apply_discount(prod, 2.0)
File "<input>", line 4, in apply_discount
assert 0 <= price <= product['price']
AssertionError
2
3
4
5
6
7
Когда пытаемся применить недопустимую скидку, наша программа останавливается с исключением AssertionError
. Это происходит потому, что 200 %-ная скидка нарушила условие утверждения assert
.
Видно отчет об этом исключении и то, как он указывает на точную строку исходного кода, содержащую вызвавшее сбой утверждение. Если во время проверки интернет-магазина вы (или другой разработчик в вашей команде) когда-нибудь столкнетесь с одной из таких ошибок, вы легко узнаете, что произошло, просто посмотрев на отчет об обратной трассировке исключения.
Это значительно ускорит отладку и в дальнейшем сделает ваши программы удобнее в поддержке. В этом и заключается сила assert
!
Почему не применить обычное исключение?
Теперь подумаем, почему в предыдущем примере просто не применить инструкцию if
и исключение.
Дело в том, что инструкция assert
предназначена для того, чтобы сообщать разработчикам о неустранимых ошибках в программе. Инструкция assert
не предназначена для того, чтобы сигнализировать об ожидаемых ошибочных условиях, таких как ошибка «Файл не найден», где пользователь может предпринять корректирующие действия или просто попробовать еще раз.
Инструкции призваны быть внутренними самопроверками (internal selfchecks) вашей программы. Они работают путем объявления неких условий, возникновение которых в вашем исходном коде невозможно. Если одно из таких условий не сохраняется, то это означает, что в программе есть ошибка.
Если ваша программа бездефектна, то эти условия никогда не возникнут. Но если же они возникают, то программа завершится аварийно с исключением AssertionError
, говорящим, какое именно «невозможное» условие было вызвано. Это намного упрощает отслеживание и исправление ошибок в ваших программах.
А пока имейте в виду, что инструкция assert
— это средство отладки, а не механизм обработки ошибок исполнения программы. Цель использования инструкции assert
состоит в том, чтобы позволить разработчикам как можно скорее найти вероятную первопричину ошибки. Если в вашей программе ошибки нет, то исключение AssertionError
никогда не должно возникнуть.
Давайте взглянем поближе на другие вещи, которые мы можем делать с инструкцией assert
, а затем я покажу две распространенные ловушки, которые встречаются во время ее использования в реальных сценариях.
Прежде чем вы начнете применять какое-то функциональное средство языка, всегда неплохо подробнее познакомиться с тем, как оно практически реализуется в Python. Поэтому давайте бегло взглянем на синтаксис инструкции assert
в соответствии с документацией Pythonopen in new window:
инструкция_assert ::= "assert" logical_expression ["," error_message]
logical_expression
— это условие, которое мы проверяем,error_message
(необязательное) — это сообщение об ошибке, которое выводится на экран, если утверждение дает сбой.
Во время исполнения программы интерпретатор Python преобразовывает каждую инструкцию assert
примерно в следующую ниже последовательность инструкций:
if __debug__:
if not logical_expression:
raise AssertionError(error_message)
2
3
В этом фрагменте кода есть две интересные детали.
Перед тем как данное условие инструкции assert
будет проверено, проводится дополнительная проверка глобальной переменной __debug__
. Это встроенный булев флажок, который при нормальных обстоятельствах имеет значение True
, — и значение False
, если запрашивается оптимизация. Мы поговорим об этом подробнее чуть позже в разделе, посвященном «распространенным ловушкам».
Кроме того, вы можете применить error_message
, чтобы передать необязательное сообщение об ошибке, которое будет показано в отчете об обратной трассировке вместе с исключением AssertionError
. Это может еще больше упростить отладку. Например, исходный код такого плана:
if cond == 'x':
do_x()
elif cond == 'y':
do_y()
else:
assert False, ('''Это никогда не должно произойти, и тем не менее это временами происходит. Сейчас мы пытаемся выяснить причину. Если вы столкнетесь с этим на практике, то просим связаться по электронной почте с dbader. Спасибо!''')
2
3
4
5
6
Конечно это ужасно, но этот прием определенно допустим и полезен, если в одном из своих приложений вы сталкиваетесь с плавающей ошибкой.
Ловушки assert
Есть два важных предостережения, на которые стоит обратить внимание:
- Первое из них связано с внесением в приложения ошибок и рисков, связанных с нарушением безопасности.
- Второе касается синтаксической причуды, которая облегчает написание бесполезных инструкций
assert
.
Звучит довольно ужасно (и потенциально таковым и является), поэтому вам, вероятно, следует как минимум просмотреть эти два предостережения хотя бы бегло.
Предостережение № 1
Не используйте инструкции assert
для проверки данных!
Самое большое предостережение по поводу использования утверждений в Python состоит в том, что утверждения могут быть глобально отключены переключателями командной строки -O
и -OO
, а также переменной окружения PYTHONOPTIMIZE
в СPython
.
Это превращает любую инструкцию assert
в нулевую операцию: утверждения assert
просто компилируются и вычисляться не будут, это означает, что ни одно из условных выражений не будет выполнено.
Это преднамеренное проектное решение, которое используется схожим образом во многих других языках программирования. В качестве побочного эффекта оно приводит к тому, что становится чрезвычайно опасно использовать инструкции assert
в виде быстрого и легкого способа проверки входных данных.
Поясню: если в вашей программе утверждения assert
используются для проверки того, содержит ли аргумент функции «неправильное» или неожиданное значение, то это решение может быстро обернуться против вас и привести к ошибкам или дырам с точки зрения безопасности.
Давайте взглянем на простой пример, который демонстрирует эту проблему. И снова представьте, что вы создаете приложение Python с интернет-магазином. Где-то среди программного кода вашего приложения есть функция, которая удаляет товар по запросу пользователя.
Поскольку вы только что узнали об assert
, вам не терпится применить их в своем коде, и вы пишете следующую реализацию:
def delete_product(prod_id, user):
assert user.is_admin(), 'здесь должен быть администратор'
assert store.has_product(prod_id), 'Неизвестный товар'
store.get_product(prod_id).delete()
2
3
4
Приглядитесь поближе к функции delete_product
. Итак, что же произойдет, если инструкции assert
будут отключены?
В этом примере трехстрочной функции есть две серьезные проблемы, и они вызваны неправильным использованием инструкций assert
:
Проверка полномочий администратора инструкциями
assert
несет в себе опасность. Если утвержденияassert
отключены в интерпретаторе Python, то проверка полномочий превращается в нулевую операцию. И поэтому теперь любой пользователь может удалять товары. Проверка полномочий вообще не выполняется. В результате повышается вероятность того, что может возникнуть проблема, связанная с обеспечением безопасности, и откроется дверь для атак, способных разрушить или серьезно повредить данные в нашем интернет-магазине. Очень плохо.Проверка
has_product()
пропускается, когдаassert
отключена. Это означает, что методget_product()
теперь можно вызывать с недопустимыми идентификаторами товаров, что может привести к более серьезным ошибкам, — в зависимости от того, как написана наша программа. В худшем случае она может стать началом запуска DoS-атак. Например, если приложение магазина аварийно завершается при попытке стороннего лица удалить неизвестный товар, то, скорее всего, это произошло потому, что взломщик смог завалить его недопустимыми запросами на удаление и вызвать сбой в работе сервера.
Каким образом можно избежать этих проблем? Ответ таков: никогда не использовать утверждения assert
для выполнения валидации данных. Вместо этого можно выполнять проверку обычными инструкциями if
и при необходимости вызывать исключения валидации данных, как показано ниже:
def delete_product(product_id, user):
if not user.is_admin():
raise AuthError('Для удаления необходимы права админа')
if not store.has_product(product_id):
raise ValueError('Идентификатор неизвестного товара')
store.get_product(product_id).delete()
2
3
4
5
6
Этот обновленный пример также обладает тем преимуществом, что вместо того, чтобы вызывать неопределенные исключения AssertionError
, он теперь вызывает семантически правильные исключения, а именно ValueError
или AuthError
(которые мы должны были определить сами).
WARNING
Инструкции assert
, которые никогда не дают сбоя.
Удивительно легко случайно написать инструкцию assert
, которая всегда при вычислении возвращает истину. Вкратце проблема в следующем.
Когда в инструкцию assert
в качестве первого аргумента передается кортеж, assert
всегда возвращает True
и по этой причине выполняется успешно.
Например, это утверждение никогда не будет давать сбой:
assert(1 == 2, 'Это утверждение должно вызвать сбой')
Эта ситуация связана с тем, что в Python непустые кортежи всегда являются истинными. Если вы передаете кортеж в инструкцию assert
, то это приводит к тому, что условие assert
всегда будет истинным, что, в свою очередь, приводит к тому, что вышеупомянутая инструкция assert
станет бесполезной, потому что она никогда не сможет дать сбой и вызвать исключение.
По причине такого, в общем-то, не интуитивного поведения относительно легко случайно написать плохие многострочные инструкции assert
. Например, представьте, что в одном из ваших модульных тестов имеется приведенное ниже утверждение:
assert (
counter == 10,
'Это должно было сосчитать все элементы'
)
2
3
4
На первый взгляд этот тестовый случай выглядит абсолютно приемлемым. Однако он никогда не выловит неправильный результат: это утверждение assert
всегда будет давать истину, независимо от состояния переменной counter
. И в чем же тут дело? А в том, что оно подтверждает истинность объекта-кортежа.
Более свежие версии Python 3 для таких сомнительных инструкций assert
показывают синтаксическое предупреждение.
Между прочим, именно поэтому всегда следует выполнять быстрый тест при помощи своих модульных тестовых случаев. Прежде чем переходить к написанию следующего, убедитесь, что они действительно не срабатывают.
Инструкции assert - резюме
Несмотря на данные выше предостережения, я полагаю, что инструкции assert
являются мощным инструментом отладки, который зачастую недостаточно используется разработчиками Python.
Понимание того, как работают инструкции assert
и когда их применять, поможет писать программы Python, которые будет легче сопровождать и отлаживать.
Это великолепный навык, который стоит освоить, чтобы прокачать знания Python до более качественного уровня и стать всесторонним питонистом.Это позволит сэкономить бесконечные часы, которые приходится тратить на отладку.
Ключевые выводы
Инструкция
assert
— это средство отладки, которое проверяет условие, выступающее в качестве внутренней самопроверки вашей программы.Инструкции
assert
должны применяться только для того, чтобы помогать разработчикам идентифицировать ошибки. Они не являются механизмом обработки ошибок периода исполнения программы.Инструкции
assert
могут быть глобально отключены в настройках интерпретатора.