Лучшая практика для Python Assert - PullRequest
435 голосов
/ 03 июня 2009
  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 ]

694 голосов
/ 03 июня 2009

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

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


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


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

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

336 голосов
/ 03 декабря 2009

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

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

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

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

139 голосов
/ 03 июня 2009

Чтобы можно было автоматически выдавать ошибку, когда 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
117 голосов
/ 24 сентября 2013

Четыре цели assert

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

Тогда assert имеет четыре роли :

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

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

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

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

  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) и множество последующих ошибок в любом случае.

21 голосов
/ 03 декабря 2009

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

18 голосов
/ 03 июня 2009

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

class XLessThanZeroException(Exception):
    pass

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

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

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

7 голосов
/ 06 марта 2013

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

Например, рекурсивная функция (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

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

6 голосов
/ 01 сентября 2017

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

# 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

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

3 голосов
/ 24 сентября 2013

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

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

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

1 голос
/ 17 октября 2016

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

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

Пример 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 переменная 'str' имеет значение nul. Таким образом, мы спасаем пользователя от опережения в неисправной программе с помощью оператора 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
ничего не получится напечатать

...