2011-01-23 6 views
5

me encuentro escribiendo afirmaciones como éstas:pitón: impresión automática de la representación de cada componente en una expresión

if f(x, y) != z: 
    print(repr(x)) 
    print(repr(y)) 
    print(repr(z)) 
    raise MyException('Expected: f(x, y) == z') 

Me preguntaba si hay una manera de escribir una función que acepte una expresión válida en Python y una excepción clase como entrada, evalúe la expresión, y si encuentra que es falsa, imprima la representación de cada uno de los términos de nivel más bajo en la expresión y aumente la excepción dada?

# validate is the mystery function 
validate('f(x, y) == z', MyException) 
+0

Le daría la vuelta al mensaje de excepción: en lugar de decir lo que quiere, especifique lo que detecte d estar equivocado Esto da la misma expresión (! =) Que probó, en lugar de su inversa (==). –

Respuesta

2

Aquí es una implementación:

import inspect, keyword, pprint, sys, tokenize 

def value_in_frame(name, frame): 
    try: 
     return frame.f_locals[name] 
    except KeyError: 
     try: 
      return frame.f_globals[name] 
     except KeyError: 
      raise ValueError("Couldn't find value for %s" % name) 

def validate(expr, exc_class=AssertionError): 
    """Evaluate `expr` in the caller's frame, raise `exc_class` if false.""" 
    frame = inspect.stack()[1][0] 
    val = eval(expr, frame.f_globals, frame.f_locals) 
    if not val: 
     rl = iter([expr]).next 
     for typ, tok, _, _, _ in tokenize.generate_tokens(rl): 
      if typ == tokenize.NAME and not keyword.iskeyword(tok): 
       try: 
        val = value_in_frame(tok, frame) 
       except ValueError: 
        val = '???' 
       else: 
        val = repr(val) 
       print " %s: %s" % (tok, val) 
     raise exc_class("Failed to validate: %s" % expr) 

if __name__ == '__main__': 
    a = b = 3 
    validate("a + b == 5") 
+0

Cool. ¿Es esto algo que recomiendas usar, o aún sugieres usar enfoques más tradicionales? – max

1

Esto sería posible de hacer. Puede usar el compilador de Python (los detalles difieren entre las versiones, por lo que esta es solo una descripción general) para compilar la expresión dada en un AST. Luego puede compilar el AST en un objeto de código y evaluarlo (o simplemente llamar al eval en primer lugar, cualquiera que sea). Luego, si el valor es falso, inspecciona el AST para ver cómo se construye la expresión. Imprima el valor de cada elemento en la expresión a la que se accede por nombre de acuerdo con AST.

+2

¿No necesita acceso al alcance de la persona que llama? (Posiblemente posible con 'inspeccionar' o algo así, pero no es muy bueno o confiable). – delnan

+0

Cool. Sin embargo, supongo que no será portátil entre los compiladores y las versiones del compilador. Quizás haya un módulo (específico del compilador) que permita la iteración a través del AST? – max

+0

@delnan: Sí, es probable que deba pasar 'globals()' y 'locals()' a la función 'validate()'. –

2

¿Qué pasa con las afirmaciones?

assert f(x, y) != z, 'Expected: f(%r, %r) == %r'%(x,y,z) 

Editar

para añadir% r para imprimir en representación de - gracias por el comentario.

+2

Desea '% r', pero en caso contrario: sí. – delnan

+0

sí, tienes razón. Recién editado con esa modificación. Gracias por la pista. –

+0

@msavadores: Oh, funcionaría, pero lo que quería decir es que las expresiones serían diferentes cada vez. Podría ser 'f (x, y)! = Z', la próxima vez podría ser' x max

1

Puede haber una manera de hackear algo que ver con lo que pides, pero las alternativas son, al menos, mucho más fácil:

  1. Como que tiene ahora, ampliando manualmente los valores de interés. Es probable que esto sea más confiable, especialmente para expresiones complejas, porque es más fácil para usted decir lo que es interesante que la computadora para deducirlo. En particular, es posible que desee incluir un valor que ni siquiera está en la expresión.

    if f(x, y) != z: 
        raise MyException("Unexpected: the foobar is glowing; " + 
            "f(%r, %r) != %r" % (x, y, z)) 
    

    Aquí, parecen querer X e Y, pero no (el resultado de la llamada) f (x, y), mientras que en otros casos que podrían dar la vuelta, o es posible que desee a los tres. Un algoritmo tendrá dificultades para determinar su preferencia.

  2. Puede iniciar pdb en el punto donde detecta la falla, examinar lo que quiera (incluso subir la pila de llamadas), y luego reanudar la ejecución "normal". Esto solo es posible en algunos contextos; p.ej. probablemente no para una aplicación web.

    if f(x, y) != z: 
        import pdb 
        pdb.set_trace() 
        raise MyException("Unexpected: ...") 
    
  3. El módulo de registro en lugar de instrucciones de impresión o en el mensaje de excepción.

1

El corredor de prueba nose tiene un plug-in llamado "detalle fracaso" que proporciona precisamente este servicio: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/plugins/failuredetail.html. Esta solución es mejor que lo que ha pedido, porque la expresión no es una cadena, es una afirmación real que luego se introspecta para encontrar el código fuente que se analizará.

Un ejemplo está en el older docs:

En otras palabras, si usted tiene una prueba como:

def test_integers(): 
    a = 2 
    assert a == 4, "assert 2 is 4" 

Usted recibirá una salida como:

File "/path/to/file.py", line XX, in test_integers: 
     assert a == 4, "assert 2 is 4" 
AssertionError: assert 2 is 4 
    >> assert 2 == 4, "assert 2 is 4" 
1

que podría hacerlo a la inversa:

expr = 'f(%r, %r) != %r' % (x,y,z) 

if eval(expr): 
    raise MyException(expr) 

O en otras palabras:

def validate(expr,myexception): 
    if eval(expr): 
     raise myexception(expr) 

bastante sucio aunque :)

+0

% r en vez de% s, y fallará con un error de sintaxis para muchos tipos. Muy sucia; no recomendado. –

+0

Reparado. De hecho, no usaría eso en mi propio código ... –

Cuestiones relacionadas