Утверждение Python и возможность повторного использования кода

Лучшей практикой, по-видимому, является использование assert для условия, которое никогда не должно произойти, если код правильный, и исключения для условия, которое немного необычное, но может произойти (например, когда заканчивается память или пользовательский ввод недействителен, или внешние соединения разорваны). Я понимаю смысл этой практики следующим образом:

  1. assert будет отключен с флагом интерпретатора -O. Условия, которые могут возникнуть из-за внешних факторов, нельзя молча игнорировать, поэтому утверждать, что это неуместно. OTOH, условия, которые могут возникнуть только в том случае, если мой код неверен, надеюсь, будут устранены с помощью тестирования и отладки, поэтому assert в порядке.

  2. assert отговаривает вызывающую сторону от обработки исключения, поскольку AssertionError обычно интерпретируется как "не поймайте меня, это катастрофический сбой". Кроме того, это слишком общее, чтобы поймать. Это идеально, когда обнаружена ошибка; типичной обработкой для этого будет остановка выполнения и отладка кода. Нехорошо, если это обычное состояние, вызванное внешними причинами.

Предположим, я пишу некоторый код, в котором я гарантирую, что определенный аргумент функции всегда положителен. Если я нахожу его отрицательным, очевидно, я допустил ошибку в коде. Следовательно, я собираюсь assert утверждать, что аргумент положителен.

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

Поскольку почти любой код потенциально может быть использован повторно однажды, часто без моего ведома, этот аргумент, кажется, говорит: «никогда не используйте assert; используйте только исключения». Очевидно, что это не общепринятая практика. Что мне не хватает?

РЕДАКТИРОВАТЬ:

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

  • создать исключение
  • провалить утверждение
  • продолжить выполнение, что, вероятно, приведет к неправильному выводу

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


person max    schedule 04.04.2012    source источник
comment
Нет, этот аргумент говорит: Никогда не используйте assert для проверки значений извне. Если вы получаете площадь чего-то из какой-то внутренней вспомогательной функции, возможно, стоит assert area >= 0 (в вызывающем коде или рядом со сложной символьной математикой, выполняющей вычисления).   -  person    schedule 05.04.2012
comment
@delnan Допустим, я тестирую аргумент, переданный в качестве входных данных для функции. В моем коде функция вызывается десятки раз. Я бы не хотел заменять один assert x > 0 в начале функции десятками повторений того же самого вне функции. Хуже того, в другой раз при вызове функции кто-то забудет добавить assert.   -  person max    schedule 05.04.2012
comment
Кстати, вот пара ответов, которые я прочитал на эту тему. stackoverflow.com/a/945135/336527. stackoverflow.com/a/3721183/336527. Все они утверждают, что ошибка должна приводить к AssertionError, но не дали понять, что это должна быть ошибка в области, где находится assert. Я полагаю, это имелось в виду?   -  person max    schedule 05.04.2012


Ответы (2)


Если функция, которую вы пишете/повторно используете, действительна с положительными или отрицательными числами, это не метод, который должен содержать утверждение. Функция, вызывающая повторно используемую функцию, должна иметь утверждение, потому что это функция, предоставляющая недопустимые значения функции.

function x() {
   var i;
   // logic to set i. use assertion to test the logic.
   assert(i > 0);
   reusedFunc(i);
}

если reusedFunc(i) недействителен с отрицательными числами, он должен генерировать исключение, если передано отрицательное значение.

person Colin D    schedule 04.04.2012
comment
Таким образом, вы бы использовали assert только для обнаружения ошибки в коде из текущей или внутренней области видимости? Я никогда не видел, чтобы это было четко сформулировано, но это звучит как хороший подход. Дай мне подумать об этом. Кстати, я имею в виду Python, но в приведенном вами примере это не имеет значения. - person max; 05.04.2012

Операторы assert предназначены для вещей, которые вы используете при разработке и отладке кода, а не для гарантии критических ограничений API.

В вашем примере с положительным числом проверьте значение и поднимите ValueError с полезным сообщением об ошибке, если использование отрицательного значения в вашем коде может иметь плохие последствия.

Я советую людям никогда не использовать операторы assert. Их легко написать, но они так часто неуместны.

Операторы assert также терпят неудачу, когда люди пишут длинные и решают использовать круглые скобки, чтобы оператор и сообщение занимали несколько строк... Это генерирует SyntaxWarning в современном Python о кортеже, который автор непреднамеренно создал, используя (условие, сообщение) на оператор, не являющийся функцией, но так было не всегда.

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

Если вы не используете операторы assert, ни одна из этих вещей вас никогда не укусит.

person gps    schedule 06.04.2012