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

  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 исключение?

13 ответов


чтобы иметь возможность автоматически выдавать ошибку, когда 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

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

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


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


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

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


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

генератор текущего кода не выдает код для оператора assert при запросе оптимизации во время компиляции. - Python 2.6.4 Docs

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

посмотреть PYTHONOPTIMIZE и - O-OO


четыре цели assert

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

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

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

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

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

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

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

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

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

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


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


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

class XLessThanZeroException(Exception):
    pass

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

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

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


как было сказано ранее, утверждения должны использоваться, когда ваш код никогда не должен достигать точки, то есть там есть ошибка. Вероятно, самая полезная причина, по которой я могу использовать утверждение, - это инвариант/pre/postcondition. Это то, что должно быть истинным в начале или конце каждой итерации цикла или функции.

например, рекурсивная функция (2 отдельные функции, поэтому 1 обрабатывает плохой ввод, а другой обрабатывает плохой код, потому что трудно отличить с рекурсией.) Это сделало бы очевидным, если бы я забыл написать заявление 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

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


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

# 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

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


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

  • не забудьте "заставьте его работать первым, прежде чем вы заставите его работать быстро".
    Очень немногие проценты любой программы обычно имеют отношение к ее скорости. Вы всегда можете выгнать или упростить 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!
    

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


есть фреймворк под названием JBoss слюнки текут для java, который выполняет мониторинг выполнения для утверждения бизнес-правил, который отвечает на вторую часть вашего вопроса. Однако я не уверен, есть ли такая структура для python.


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

в Примере 1, так как переменная ' str ' не равна нулю. Поэтому никаких утверждений или исключений не возникает.

Пример 1:

#!/usr/bin/python

str = 'hello Pyhton!'
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 Pyhton!
FileName ..................... hello
FilePath ..................... C:/Python\hello.py

в Примере 2 var ' str ' равен нулю. Так мы спасаем пользователь от идти впереди неисправной программы по утверждаю заявление.

Пример 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
ничего не получит печать


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


Если вы имеете дело с унаследованным кодом, который полагается на 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