Python afirma: ¿mejoró la introspección del fracaso?

Este es un error de afirmación bastante inútil; no indica los valores de la expresión involucrada (se supone que las constantes utilizadas son en realidad nombres de variables):

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

¿Hay una mejor implementación de afirmación en Python que sea más elegante? No debe introducir una sobrecarga adicional sobre la ejecución (excepto cuando falla la afirmación) .. y debe apagarse si se usa el indicador -O .

Edit : Sé sobre el segundo argumento de asert como una cadena. No quiero escribir uno … ya que está codificado en la expresión que se afirma. SECO (no se repita).

Instale su función como sys.excepthook – vea los documentos . Su función, si el segundo argumento es AssertionError , puede hacer una introspección de los contenidos de su corazón; en particular, a través del tercer argumento, el rastreo, puede obtener el marco y el lugar exacto en el que falló la afirmación, obteniendo la excepción que falla a través del código fuente o bytecode, el valor de todas las variables relevantes, etc. La inspección del módulo ayuda.

Hacerlo con total generalidad es bastante trabajo, pero dependiendo de las restricciones que esté dispuesto a aceptar en la forma en que escribe sus assert , se puede aclarar sustancialmente (por ejemplo, restringirlas a variables locales o globales hace que la introspección sea más fácil que si Las variables no locales de un cierre podrían estar involucradas, y así sucesivamente).

Como @Mark Rushakoff dijo que la nose puede evaluar afirmaciones fallidas. Funciona en la assert estándar también.

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

la ayuda de las nosetests

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

Ejemplo:

 $ 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) 

Puede adjuntar un mensaje a una assert :

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

El mensaje también se puede construir dinámicamente:

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

Consulte la statement de afirmación en la documentación de Python para obtener más información.

El conjunto de pruebas de la nariz aplica la introspección a las afirmaciones .

Sin embargo, AFAICT, debe llamar a sus afirmaciones para obtener la introspección:

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

resultados en

 C: \ temp \ py> C: \ Python26 \ Scripts \ nosetests.exe -d test.py
 F
 ================================================== ====================
 ERROR: test.test1
 -------------------------------------------------- --------------------
 Rastreo (llamadas recientes más última):
   Archivo "C: \ Python26 \ lib \ site-packages \ nose-0.11.1-py2.6.egg \ nose \ case.py", línea
 183, en runTest
     self.test (* self.arg)
   Archivo "C: \ temp \ py \ test.py", línea 3, en test1
     nose.tools.assert_equal (6, 5 + 2)
 Error de aserción: 6! = 7
 >> elevar self.failureException, \
           (Ninguno o '% r! =% R'% (6, 7))

Observe el error de aserción allí. Cuando mi línea era solo assert 6 == 5+2 , obtendría:

 C: \ temp \ py> C: \ Python26 \ Scripts \ nosetests.exe -d test.py
 F
 ================================================== ====================
 ERROR: test.test1
 -------------------------------------------------- --------------------
 Rastreo (llamadas recientes más última):
   Archivo "C: \ Python26 \ lib \ site-packages \ nose-0.11.1-py2.6.egg \ nose \ case.py", línea
 183, en runTest
     self.test (* self.arg)
   Archivo "C: \ temp \ py \ test.py", línea 2, en test1
     afirmar 6 == 5 + 2
 Error de aserción
 >> afirmar 6 == 5 + 2

Además, no estoy seguro si sus declaraciones se omiten con -O , pero eso sería una comprobación muy rápida.

Agregue un mensaje a su aserción, que se mostrará si la aserción falla:

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

La única forma en la que se me ocurre proporcionar esto automáticamente sería contener la afirmación en una llamada de procedimiento y luego inspeccionar la stack para obtener el código fuente de esa línea. Desafortunadamente, la llamada adicional introduciría los gastos generales en la prueba y no se desactivaría con -O .

Parece que lo que realmente quiere hacer es configurar un punto de interrupción del depurador justo antes de la assert e inspección de su depurador favorito tanto como desee.

sys.excepthook un reemplazo para sys.excepthook (que se llama para cualquier excepción no controlada) que es un poco más sofisticado que el estándar. Analizará la línea donde se produjo la excepción e imprimirá todas las variables a las que se hace referencia en esta línea (no imprime todas las variables locales porque puede ser demasiado ruido, también, tal vez la var importante sea global o algo así).

Lo llamé py_better_exchook (nombre perfecto) y está aquí .

Ejemplo de archivo:

 a = 6 def test(): unrelated_var = 43 b,c = 2, 3 assert a - b*c import better_exchook better_exchook.install() test() 

Salida:

 $ python test_error_reporting.py EXCEPTION Traceback (most recent call last): File "test_error_reporting.py", line 12, in  line: test() locals: test =   File "test_error_reporting.py", line 7, in test line: assert a - b*c locals: a =  6 b =  2 c =  3 AssertionError 

Hay algunas otras alternativas:

  • (Presentado aquí) https://github.com/albertz/py_better_exchook/
  • https://github.com/patrys/great-justice
  • Nariz hace algo similar por fallas de aserción, ver aquí .
  • IPython tiene algo similar ( esto ). Haga esto: from IPython.core import ultratb; sys.excepthook = ultratb.VerboseTB() from IPython.core import ultratb; sys.excepthook = ultratb.VerboseTB() .
  • “Cgitb.py” de Ka-Ping Yee, que forma parte de Python, consulte aquí , el código aquí .