Лучшая практика для Python Assert

483
  1. Есть ли проблемы с производительностью или поддержкой кода при использовании assert качестве части стандартного кода вместо использования его только для целей отладки?

    Является

    assert x >= 0, 'x is less than zero'

    лучше или хуже чем

    if x < 0:
        raise Exception, 'x is less than zero'
  2. Кроме того, есть ли способ установить бизнес-правило, подобное if x < 0 raise errorтому, которое всегда проверяется без него try/except/finally, если в любое время кода xменьше 0 возникает ошибка, как если бы вы установили assert x < 0в начале функции где-нибудь внутри функции где xстановится меньше 0, исключение возбуждается?

Meade
источник
3
Для записи: mail.python.org/pipermail/python-list/2013-November/660401.html
Томаш Зелиньски,
29
Параметры Python -O и -OO отбросят ваши утверждения. Это должно побудить вас задуматься о том, для чего это нужно.
Питер Лада
4
Ссылка Томаса Зелинского была разорвана, теперь она: mail.python.org/pipermail/python-list/2013-November/660568.html . Я почти уверен, что у pipermail нестабильная функция ID, я нашел другие ссылки внутри того же pipermail, указывающие на тот же URL с тем же намерением.
quodlibetor
3
Если mail.python.org/pipermail/python-list/2013-November/660568.html перемещается снова, он архивируется по адресу archive.is/5GfiG . Заголовок поста «Когда использовать assert» и является отличным постом (действительно статья) о лучших практиках для Python assert.
клак

Ответы:

144

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

class LessThanZeroException(Exception):
    pass

class variable(object):
    def __init__(self, value=0):
        self.__x = value

    def __set__(self, obj, value):
        if value < 0:
            raise LessThanZeroException('x is less than zero')

        self.__x  = value

    def __get__(self, obj, objType):
        return self.__x

class MyClass(object):
    x = variable()

>>> m = MyClass()
>>> m.x = 10
>>> m.x -= 20
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "my.py", line 7, in __set__
    raise LessThanZeroException('x is less than zero')
LessThanZeroException: x is less than zero
Надя Алрамли
источник
10
Хотя свойства реализованы как дескрипторы, я бы не назвал это примером их использования. Это еще один пример свойств самих по себе: docs.python.org/library/functions.html#property
Джейсон Бейкер
3
Свойства должны использоваться в MyClass при установке x. Это решение слишком общее.
114
Довольно хороший ответ, нравится, но не имеет ничего общего с вопросом ... Разве мы не можем отметить ответ Дистана или Джона Ми как действительный ответ?
Вайк Хермеч
4
Это не кажется, чтобы ответить на заголовок вопроса. Кроме того, это плохая альтернатива функции свойств класса Python.
Dooms101
10
@VajkHermecz: На самом деле, если вы перечитаете вопрос, это два вопроса в одном. Люди, только смотрящие на название, знакомы только с первым вопросом, на который этот ответ не отвечает. Этот ответ на самом деле содержит ответ на второй вопрос.
ArtOfWarfare,
742

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

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


Например, если вы пишете функцию для чтения из файла конфигурации в файл dict, неправильное форматирование в файле должно вызвать a ConfigurationSyntaxError, а вы можете assertэтого не возвращать None.


В вашем примере, если x это значение, установленное через пользовательский интерфейс или из внешнего источника, лучше использовать исключение.

Если x установлен только вашим собственным кодом в той же программе, используйте утверждение.

Deestan
источник
126
Это правильный способ использования утверждений. Они не должны использоваться для управления потоком программ.
Тейн Бримхолл
41
+1 за последний абзац - хотя вы должны явно упомянуть, что он assertсодержит неявное if __debug__и может быть оптимизировано - как говорится в ответе Джона Ми
Тобиас Кинцлер
3
Перечитывая свой ответ, я думаю, вы, вероятно, не имели в виду условия, которые никогда не должны рассматриваться как правило, а скорее цель состоит в том, чтобы завершить работу рано в случае поврежденного состояния программы, которое обычно совпадает с условием, которого вы не ожидаете когда-либо случиться .
Bentley4
10
assert следует использовать только для обнаружения проблем без какого-либо известного восстановления; почти всегда ошибки кода (не плохие входы). когда инициируется утверждение, это должно означать, что программа находится в состоянии, в котором может быть опасно продолжать работу, так как она может начать общаться с сетью или записывать на диск. Надежный код «атомарно» переходит из допустимого состояния в допустимое состояние перед лицом неверного (или вредоносного) ввода. верхний уровень каждой нити должен иметь защитный барьер. барьеры отказов, которые потребляют входные данные из внешнего мира, обычно терпят неудачу только для одной итерации барьера (в то время как / try), откат / ошибка входа в систему.
Роб
10
«Утверждения должны использоваться для проверки условий, которые никогда не должны происходить». Да. И смысл второго «должен»: если это произойдет, код программы будет неправильным.
Латц Пречелт
362

Операторы assert удаляются при оптимизации компиляции . Так что, да, есть как производительность, так и функциональные различия.

Текущий генератор кода не генерирует код для оператора assert, когда во время компиляции запрашивается оптимизация. - Документы Python 2 Документы Python 3

Если вы используете assert для реализации функциональности приложения, а затем оптимизируете развертывание в рабочей среде, вас будут мучить дефекты «но это работает в dev».

См. ПИТОНОПТИМИЗАЦИЯ и -O -OO

Джон Ми
источник
26
Вот Это Да! Супер важное замечание, которое есть! Я планировал с помощью утверждений проверить несколько вещей, которые никогда не должны потерпеть неудачу, отказ которых будет означать, что кто-то очень осторожно манипулирует моими данными, которые он отправляет, в попытке получить доступ к данным, к которым у него не должно быть доступа. Это не сработает, но я хочу быстро прекратить их попытки с утверждением, так что их оптимизация в производстве потерпела бы поражение. Я думаю , я просто вместо этого. О - я только что обнаружил удачно названное имя в подклассах ! Отлично! raiseExceptionSuspiciousOperation ExceptionDjango
ArtOfWarfare
Кстати, @ArtOfWarfare, если вы запустите banditсвой код, он предупредит вас об этом.
Нагев
132

Четыре цели assert

Предположим, вы работаете над 200 000 строк кода с четырьмя коллегами - Алисой, Берндом, Карлом и Дафни. Они называют ваш код, вы называете их код.

Затем assertимеет четыре роли :

  1. Сообщите Алисе, Бернду, Карлу и Дафни, что ожидает ваш код.
    Предположим, у вас есть метод, который обрабатывает список кортежей, и логика программы может нарушиться, если эти кортежи не являются неизменяемыми:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    Это более достоверно, чем эквивалентная информация в документации, и намного проще в обслуживании.

  2. Сообщите компьютеру, что ожидает ваш код.
    assertобеспечивает правильное поведение от вызывающих вашего кода. Если ваш код вызывает код Алисы, а код Бернда - ваш, то без того assert, если в программе происходит сбой в коде Алисы, Бернд может предположить, что это вина Алисы, Алиса расследует и может предположить, что это ваша ошибка, вы расследуете и говорите Бернду, что это на самом деле его. Много работы потеряно.
    С утверждениями, кто бы ни получил неправильный вызов, они быстро смогут увидеть, что это их вина, а не ваша. Алиса, Бернд, и вам всем это выгодно. Экономит огромное количество времени.

  3. Сообщите читателям своего кода (включая себя), чего достиг ваш код в какой-то момент.
    Предположим, у вас есть список записей, и каждая из них может быть чистой (что хорошо) или может быть smorsh, trale, gullup или twinkled (что все неприемлемо). Если это сморш, то должно быть безмятежно; если это trale, это должно быть baludoed; если это гуллуп, то нужно идти рысью (и затем, возможно, тоже ходить); если он мерцает, он должен мигать снова, кроме четверга. Вы поняли: это сложные вещи. Но конечным результатом является (или должно быть), что все записи чисты. Правильная вещь (TM), чтобы сделать, состоит в том, чтобы суммировать эффект Вашей очистки цикла как

    assert(all(entry.isClean() for entry in mylist))

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

  4. Сообщите компьютеру, чего достиг ваш код в какой-то момент.
    Если вы когда-нибудь забудете пройти запись, нуждающуюся в ней, после рычага, это assertспасет ваш день и позволит избежать того, что ваш код сломается, дорогая Дафни намного позже.

На мой взгляд, assertдве цели документации (1 и 3) и защиты (2 и 4) одинаково ценны.
Информирование людей может быть даже более ценным, чем информирование компьютера, потому что оно может предотвратить те самые ошибки, которые assertнужно поймать (в случае 1), и множество последующих ошибок в любом случае.

Лутц Пречелт
источник
34
5. assert isinstance () помогает PyCharm (Python IDE) узнать тип переменной, она используется для автозаполнения.
Cjkjvfnby
1
Утверждает исходные предположения кода для того, что является истинным в текущее время выполнения. Это предположение комментарий, который проверяется.
Pyj
9
Относительно 2 и 4: Вы должны быть очень осторожны, чтобы ваши утверждения не были слишком строгими. Иначе сами утверждения могут быть единственной вещью, удерживающей вашу программу для более общего использования. Особенно утверждение типов идет вразрез с типизацией Python.
zwirbeltier
9
@Cjkjvfnby Будьте осторожны с чрезмерным использованием isinstance (), как описано в этой записи блога: « isinstance () считается вредным ». Теперь вы можете использовать строки документации для указания типов в Pycharm.
binarysubstrate
2
Использование утверждений в одном из способов обеспечения договора. Подробнее о дизайне по контракту en.wikipedia.org/wiki/Design_by_contract
Лешек Зарна
22

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

outis
источник
3
Правильно. Казалось бы глупо отлавливать исключения ошибок утверждений в вызывающей стороне.
Раффи Хачадурян
Очень хороший момент. Нюанс, который может быть легко упущен при рассмотрении оригинальных вопросов на макроуровне. Даже если бы не проблема с утверждениями, отбрасываемыми при оптимизации, потеря конкретной информации о том, какая ошибка произошла, усложнила бы отладку. Ура, Outis!
cfwschmidt
Ваш ответ может быть прочитан так, как будто вы можете захотеть поймать AssertionErrors, когда вы в порядке, когда он грубый. На самом деле, вы не должны ловить их.
Томаш Гандор
19

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

class XLessThanZeroException(Exception):
    pass

def CheckX(x):
    if x < 0:
        raise XLessThanZeroException()

def foo(x):
    CheckX(x)
    #do stuff here

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

Джейсон Бейкер
источник
24
Вы можете добавить сообщение об ошибке в утверждение. Это второй параметр. Это сделает это описательным.
Раффи Хачадурян
10

Слово « утверждать» на английском языке используется в смысле ругаться , утверждать , заявлять . Это не значит «проверить» или «должно быть» . Это означает, что вы как кодер делаете здесь присяжные заявления :

# I solemnly swear that here I will tell the truth, the whole truth, 
# and nothing but the truth, under pains and penalties of perjury, so help me FSM
assert answer == 42

Если код правильный, за исключением сбоев одного события, сбоев оборудования и т. Д., Никакое утверждение никогда не завершится неудачей . Вот почему поведение программы для конечного пользователя не должно быть затронуто. Особенно утверждение не может потерпеть неудачу даже в исключительных программных условиях . Такого просто не бывает. Если это произойдет, программист должен быть зарезан за это.

Антти Хаапала
источник
8

Как уже было сказано ранее, утверждения следует использовать, когда ваш код НЕ ДОЛЖЕН когда-либо достигать точки, то есть там есть ошибка. Вероятно, наиболее полезная причина, по которой я вижу использование утверждения, - это инвариант / пред / постусловие. Это то, что должно быть верно в начале или конце каждой итерации цикла или функции.

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

def SumToN(n):
    if n <= 0:
        raise ValueError, "N must be greater than or equal to 0"
    else:
        return RecursiveSum(n)

def RecursiveSum(n):
    #precondition: n >= 0
    assert(n >= 0)
    if n == 0:
        return 0
    return RecursiveSum(n - 1) + n
    #postcondition: returned sum of 1 to n

Эти инварианты цикла часто могут быть представлены с утверждением.

matts1
источник
2
Лучше всего это делать с декораторами (@precondition и @postcondition)
Caridorc
@Caridorc, в чем конкретная выгода?
Чиль тен Бринке
@ChieltenBrinke самодокументирующий код, вместо того, #precondition: n >= 0 чтобы утверждать, он может просто написать@precondition(lambda n: n >= 0)
Caridorc
@Caridorc Это встроенные декораторы? И как из этого генерировать документацию?
Чиль тен Бринке
@ChieltenBrinke не встроенный, но простой в реализации stackoverflow.com/questions/12151182/… . Для документации просто исправьте__doc__
исправьте
4

Есть ли проблема с производительностью?

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

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

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

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

    def mymethod(listOfTuples):
        assert(type(listOfTuples[0])==tuple)  # in fact _all_ must be tuples!

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

Лутц Пречелт
источник
2
Должно быть assert(len(listOfTuples)==0 or type(listOfTyples[0])==tuple).
Ос
Нет, не должно. Это было бы намного более слабым тестом, потому что он больше не проверяет свойство «непустое», которое проверяется вторым утверждением. (Первый нет, хотя и должен.)
Lutz Prechelt
1
Второе утверждение явно не проверяет непустое свойство; это скорее побочный эффект. Если бы возникло исключение из-за того, что список пуст, то человек, работающий с кодом (кто-то еще или автор, через год после его написания), смотрел бы на него, пытаясь выяснить, действительно ли утверждение предназначено для отлова. ситуация с пустым списком или ошибка в самом утверждении. Кроме того, я не вижу, как не проверка на пустой случай "намного слабее", тогда как проверка только первого элемента является "правильной на 97%".
OSA
3

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

Цель

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

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

Стиль

В Python assertэто утверждение, а не функция! (помните assert(False, 'is true'), не поднимет. Но, имея это в стороне:

Когда и как написать необязательное «сообщение об ошибке»?

Это на самом деле относится к платформам модульного тестирования, которые часто имеют много специальных методов для выполнения утверждений ( assertTrue(condition)и assertFalse(condition), assertEqual(actual, expected)т. Д.). Они также часто дают возможность прокомментировать утверждение.

В одноразовом коде вы можете обойтись без сообщений об ошибках.

В некоторых случаях нечего добавить к утверждению:

def dump (что-то): утверждение isinstance (что-то, Dumpable) # ...

Но кроме этого, сообщение полезно для общения с другими программистами (которые иногда являются интерактивными пользователями вашего кода, например, в Ipython / Jupyter и т. Д.).

Дайте им информацию, а не только утечки внутренних деталей реализации.

вместо:

assert meaningless_identifier <= MAGIC_NUMBER_XXX, 'meaningless_identifier is greater than MAGIC_NUMBER_XXX!!!'

записывать:

assert meaningless_identifier > MAGIC_NUMBER_XXX, 'reactor temperature above critical threshold'

или, может быть, даже:

assert meaningless_identifier > MAGIC_NUMBER_XXX, f'reactor temperature({meaningless_identifier }) above critical threshold ({MAGIC_NUMBER_XXX})'

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

Негативное или позитивное сообщение?

Это может быть спорным, но мне больно читать такие вещи, как:

assert a == b, 'a is not equal to b'
  • это две противоречивые вещи, написанные рядом друг с другом. Поэтому, когда бы я ни влиял на кодовую базу, я настаиваю на том, чтобы указать, что мы хотим, используя дополнительные глаголы, такие как «должен» и «должен», а не говорить то, что нам не нужно.

    утверждать a == b, 'a должно быть равно b'

Затем получение AssertionError: a must be equal to bтакже читабельно, и выражение выглядит логичным в коде. Кроме того, вы можете получить что-то из этого, не читая трассировку (которая иногда может даже не быть доступной).

Томаш Гандор
источник
1

Как использование, так assertи возникновение исключений касаются общения.

  • Утверждения - это утверждения о правильности кода, адресованные разработчикам . Утверждение в коде информирует читателей кода об условиях, которые должны быть выполнены для правильности кода. Утверждение, которое не выполняется во время выполнения, сообщает разработчикам о наличии дефекта в коде, который необходимо исправить.

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

Лучшая практика

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

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

Есть ли проблема с [...] производительностью при использовании assert

Оценка утверждений занимает некоторое время. Однако они могут быть устранены во время компиляции. Это имеет некоторые последствия, однако, см. Ниже.

Есть ли проблема с поддержкой кода при использовании assert

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

Дирк Херрманн
источник
1

Утверждение должно проверить -
1. правильное условие,
2. правильное утверждение,
3. истинная логика;
исходного кода. Вместо того, чтобы проваливать весь проект, он дает сигнал, что что-то не подходит в вашем исходном файле.

В примере 1, поскольку переменная 'str' не равна нулю. Таким образом, никакие утверждения или исключения не будут подняты.

Пример 1:

#!/usr/bin/python

str = 'hello Python!'
strNull = 'string is Null'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
hello Python!
FileName ..................... hello
FilePath ..................... C:/Python\hello.py

В примере 2 переменная 'str' равна нулю. Таким образом, мы спасаем пользователя от опережающей ошибочной программы оператором assert .

Пример 2:

#!/usr/bin/python

str = ''
strNull = 'NULL String'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
AssertionError: NULL String

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

python -O assertStatement.py
ничего не получит печать

ДАК
источник
0

В таких средах разработки, таких как PTVS, PyCharm, assert isinstance()операторы Wing могут использоваться для включения завершения кода для некоторых неясных объектов.

denfromufa
источник
Это, кажется, предшествует использованию аннотаций типа или of typing.cast.
Acumenus
-1

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

try:
    assert False
    raise Exception('Python assertions are not working. This tool relies on Python assertions to do its job. Possible causes are running with the "-O" flag or running a precompiled (".pyo" or ".pyc") module.')
except AssertionError:
    pass
Эмилио М Бумачар
источник
2
Это не отвечает на вопрос ОП, касающийся лучших практик.
codeforester