Python assert - улучшенная интроспекция отказа?

Это довольно бесполезная ошибка утверждения; он не указывает значения задействованного выражения (предполагается, что используемые константы являются фактически именами переменных):

$ python -c "assert 6-(3*2)"
[...]
AssertionError

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

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

Ответ 1

Установите свою функцию как sys.excepthook - см. документы. Ваша функция, если второй аргумент AssertionError, может проникнуть в ваше сердечное содержимое; в частности, через третий аргумент, трассировку, он может получить фрейм и точное место, в котором не удалось выполнить утверждение, получить исключение сбоя через источник или байт-код, значение всех соответствующих переменных и т.д. Модуль inspect.

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

Ответ 2

Как @Mark Rushakoff сказал nose может оценить неудавшиеся утверждения. Он также работает со стандартным assert.

# test_error_reporting.py
def test():
    a,b,c = 6, 2, 3
    assert a - b*c

nosetests 'help:

$ nosetests --help|grep -B2 assert
  -d, --detailed-errors, --failure-detail
                        Add detail to error output by attempting to evaluate
                        failed asserts [NOSE_DETAILED_ERRORS]

Пример:

$ nosetests -d
F
======================================================================
FAIL: test_error_reporting.test
----------------------------------------------------------------------
Traceback (most recent call last):
  File "..snip../site-packages/nose/case.py", line 183, in runTest
    self.test(*self.arg)
  File "..snip../test_error_reporting.py", line 3, in test
    assert a - b*c
AssertionError:
    6,2,3 = 6, 2, 3
>>  assert 6 - 2*3


----------------------------------------------------------------------
Ran 1 test in 0.089s

FAILED (failures=1)

Ответ 3

Вы можете прикрепить сообщение к assert:

assert 6-(3*2), "always fails"

Сообщение также может быть построено динамически:

assert x != 0, "x is not equal to zero (%d)" % x

Для получения дополнительной информации см. инструкцию assert в документации Python.

Ответ 4

Набор для тестирования носа применяет интроспекцию к утверждениям.

Однако, AFAICT, вы должны вызвать их утверждения, чтобы получить интроспекцию:

import nose
def test1():
    nose.tools.assert_equal(6, 5+2)

приводит к

C:\temp\py>C:\Python26\Scripts\nosetests.exe -d test.py
F
======================================================================
FAIL: test.test1
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python26\lib\site-packages\nose-0.11.1-py2.6.egg\nose\case.py", line
183, in runTest
    self.test(*self.arg)
  File "C:\temp\py\test.py", line 3, in test1
    nose.tools.assert_equal(6, 5+2)
AssertionError: 6 != 7
>>  raise self.failureException, \
          (None or '%r != %r' % (6, 7))

Обратите внимание на AssertionError. Когда моя строка была просто assert 6 == 5+2, я бы получил:

C:\temp\py>C:\Python26\Scripts\nosetests.exe -d test.py
F
======================================================================
FAIL: test.test1
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python26\lib\site-packages\nose-0.11.1-py2.6.egg\nose\case.py", line
183, in runTest
    self.test(*self.arg)
  File "C:\temp\py\test.py", line 2, in test1
    assert 6 == 5 + 2
AssertionError:
>>  assert 6 == 5 + 2

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

Ответ 5

Добавьте сообщение в ваше утверждение, которое будет отображаться, если утверждение не выполнено:

$ python -c "assert 6-(3*2), '6-(3*2)'"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AssertionError: 6-(3*2)

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

Ответ 6

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

Ответ 7

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

Я назвал его py_better_exchook (совершенное имя) и здесь.

Файл примера:

a = 6

def test():
    unrelated_var = 43
    b,c = 2, 3
    assert a - b*c

import better_exchook
better_exchook.install()

test()

Вывод:

$ python test_error_reporting.py 
EXCEPTION
Traceback (most recent call last):
  File "test_error_reporting.py", line 12, in <module>
    line: test()
    locals:
      test = <local> <function test at 0x7fd91b1a05f0>
  File "test_error_reporting.py", line 7, in test
    line: assert a - b*c
    locals:
      a = <global> 6
      b = <local> 2
      c = <local> 3
AssertionError