Лучшая практика для Python assert
-
есть ли проблема с производительностью или обслуживанием кода с использованием
assert
как часть стандартного кода вместо использования его только для целей отладки?и
assert x >= 0, 'x is less than zero'
лучше или хуже, чем
if x < 0: raise Exception, 'x is less than zero'
кроме того, есть ли способ установить бизнес-правило, например
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
и четыре роли:
-
сообщите Алисе, Бернду, Карлу и Дафне, что ожидает ваш код.
Предположим у вас есть метод, который обрабатывает список кортежей и логика программы может сломаться, если эти кортежи не неизменны:def mymethod(listOfTuples): assert(all(type(tp)==tuple for tp in listOfTuples))
это более достоверно, чем эквивалентная информация в документации и гораздо легче поддерживать.
сообщите компьютеру, что ожидает ваш код.
assert
обеспечивает надлежащее поведение вызывающих абонентов вашего кода. Если ваш код вызывает код Алисы и Бернда вызывает ваш, тогда безassert
, если программа аварийно завершает работу в коде Alices, Бернд мог бы подумать, что во всем виновата Алиса., Алиса расследует и может предположить, что это была ваша вина, вы расследуете дело и скажете Бернду, что это на самом деле его. Много работы потеряно.
С утверждениями, кто бы ни получил неправильный вызов, они быстро смогут увидеть, что это было их вина, не твоя. Элис, Бернд и вы все выиграете. Сохраняет огромное количество времени.-
сообщите читателям вашего кода (включая себя), что ваш код достиг в какой-то момент.
Предположим, у вас есть список записей и каждый из них может быть чистым (что хорошо) или это может быть smorsh, trale, gullup или twinkled (которые все неприемлемы). Если это smorsh она должна быть unsmorshed; если это trale она должна быть baludoed; если это галлап, он должен быть рысью (а затем, возможно, и шагал); если он мерцает, то должен мерцать снова, кроме четвергов. Вы понимаете: это сложная штука. Но конечный результат (или должен быть) заключается в том, что все записи чисты. Правильная вещь (TM) - суммировать эффект ваш петля очистки asassert(all(entry.isClean() for entry in mylist))
это заявление экономит головную боль для всех, кто пытается понять что?!--42-->ровно это то, что замечательный цикл достигает. И самым частым из этих людей, скорее всего, будете вы сами.
сообщите компьютеру, что ваш код достиг в какой-то момент.
Если вы забыли в ПАСЕ запись понадобится после рысистых, theassert
сохранит ваш день и избежит этого ваш код разбивает дорогая Дафна намного позже.
на мой взгляд, 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