2009-08-20 14 views
10

Este es un error de aserción bastante inútil; no le dice a los valores de la expresión que se trate (suponer constantes utilizadas son en realidad los nombres de variables):Python assert: ¿mejor introspección de fallas?

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

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

Editar: Sé sobre el segundo argumento de assert como una cadena. No quiero escribir uno ... ya que está codificado en la expresión que se afirma. DRY (No repetir)

Respuesta

5

puede adjuntar un mensaje a un assert:

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

El mensaje también se puede construir de forma dinámica:

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

Ver The assert statement en la documentación de Python para más información.

+0

Por supuesto, sabía que esto. No quiero escribir uno ya que está codificado en la expresión que se afirma. SECO. –

+0

Veo lo que quieres decir. No creo que Python tenga una forma de hacer esto. –

0

Añadir un mensaje a su afirmación, que se mostrará si la afirmación falla:

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

La única manera que puedo pensar para proporcionar esto de forma automática sería para contener la afirmación de una llamada de procedimiento, y luego inspecciona la pila para obtener el código fuente de esa línea. La llamada adicional, desafortunadamente, introduciría gastos generales en la prueba y no se inhabilitaría con -O.

+0

Precisamente. Deshabilitar esta 'introspección' en -O esa es la clave de la pregunta. –

+0

.. PERO esto no es una sobrecarga si esta función se llama solo durante los errores de aserción (no llamadas de aserción). –

4

The nose testing suite applies introspection to asserts.

Sin embargo, AFAICT, tiene que llamar a su afirma 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 
====================================================================== 
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)) 

Aviso del AssertionError allí. Cuando mi línea era sólo assert 6 == 5+2, me sale:

 
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 

Además, no estoy seguro de si su improvisada afirma se saltan con -O, pero eso sería un cheque muy rápido.

+0

Lo suficientemente bueno para los casos de prueba, pero para el código de producción ... hay una sobrecarga de llamadas a la función (incluso con la opción -O) –

+4

Las afirmaciones ordinarias también funcionan. Consulte http://stackoverflow.com/questions/1308607/python-assert-improved-introspection-of-failure/1309039#1309039 – jfs

7

Como @Mark Rushakoff saidnose puede evaluar afirmaciones fallidas. Funciona en el estándar assert también.

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

nosetests 'ayuda:

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

La pregunta es sobre el uso de assert en el código de la aplicación (que es invocado directamente por el usuario, para ej., ./foo.py .. o haciendo clic en 'foo.pyw' en el Explorador de Windows), y no en el código de prueba ... por lo que estoy contento con el resultado de afirmar de py.test. –

+1

@srid: En este caso, escriba: '__debug__ y your_fancy_assert (expression)' - sin tara en '-O'. – jfs

+0

Eso suena interesante; Lástima que Python no tiene una función 'macro'. –

10

Instalar el de la función como sys.excepthook - ver the docs.Su función, si el segundo argumento es AssertionError, puede introspectar al contenido de su corazón; en particular, a través del tercer argumento, la trazabilidad, puede obtener el marco y el punto exacto en el que falló la afirmación, obteniendo la excepción defectuosa a través del código fuente o byte, el valor de todas las variables relevantes, etc. El módulo inspect ayuda.

Hacerlo en toda su generalidad es absolutamente un pedazo de trabajo, pero dependiendo de lo que las restricciones que está dispuesto a aceptar en la forma de escribir sus assert s que se puede aligerar considerablemente (por ejemplo, la restricción de que sólo las variables locales o globales hace la introspección es más fácil que si pudieran estar involucradas variables no locales de un cierre, y así sucesivamente).

+1

Bueno. Ahora hay una biblioteca de Python para esto ... ¿o tengo que escribir la mía? :-) (Probablemente no lo haga ... ya que esta es una tarea de baja prioridad para mí) –

+0

Desafortunadamente no conozco las bibliotecas de Python existentes que hacen todo esto, excepto las orientadas a las pruebas (que podrían tener que adaptarse) con el propósito de usarlos en el código de producción). –

0

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

0

He codificado un reemplazo para sys.excepthook (que se llama para cualquier excepción no controlada) que es un poco más elegante que la 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 eso podría ser demasiado ruido; también, tal vez la var importante sea global o similar).

Lo llamé py_better_exchook (nombre perfecto) y es here.

archivo Ejemplo:

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