2009-10-19 15 views
52

¿Hay alguna forma de saber (en el momento de la codificación) qué excepciones esperar al ejecutar el código python? Terminé atrapando la clase de excepción base el 90% del tiempo ya que no sé qué tipo de excepción podría arrojarse (y no me pidas que lea la documentación. Muchas veces se puede propagar una excepción desde la profundidad. muchas veces la documentación no está actualizada o es correcta). ¿Hay algún tipo de herramienta para verificar esto? (como leyendo el código python y libs)?Python: ¿Cómo puedo saber qué excepciones se pueden lanzar desde un método? Llame al

+1

Tenga en cuenta que en Python <2.6, también puede 'subir' cadenas, no solo las subclases 'BaseException'. Entonces, si llama al código de la biblioteca que está fuera de su control, incluso 'excepto Excepción' no es suficiente, ya que no detectará las excepciones de cadena. Como otros han señalado, estás ladrando el árbol equivocado aquí. –

+0

No lo sabía. Pensé excepto Excepción: ... atrapa casi todo. – GabiMe

+1

'except Exception' funciona bien para capturar excepciones de cadenas en Python 2.6 y versiones posteriores. –

Respuesta

15

supongo que una solución podría ser la única imprecisa debido a la falta de reglas tipos estáticos.

No conozco alguna herramienta que verifique las excepciones, pero podría encontrar su propia herramienta que coincida con sus necesidades (una buena oportunidad para jugar un poco con el análisis estático).

En un primer intento, se podría escribir una función que construye un AST, encuentra todos los Raise nodos, y luego trata de averiguar los patrones comunes de excepciones ganadería (por ejemplo, llamar a un constructor directamente)

Vamos x ser el siguiente programa:

x = '''\ 
if f(x): 
    raise IOError(errno.ENOENT, 'not found') 
else: 
    e = g(x) 
    raise e 
''' 

Construir la AST usando el paquete de compiler:

tree = compiler.parse(x) 

A continuación, defina una clase Raise visitante:

class RaiseVisitor(object): 
    def __init__(self): 
     self.nodes = [] 
    def visitRaise(self, n): 
     self.nodes.append(n) 

y caminar por la recogida de AST Raise nodos:

v = RaiseVisitor() 
compiler.walk(tree, v) 

>>> print v.nodes 
[ 
    Raise(
     CallFunc(
      Name('IOError'), 
      [Getattr(Name('errno'), 'ENOENT'), Const('not found')], 
      None, None), 
     None, None), 
    Raise(Name('e'), None, None), 
] 

Puede continuar resolviendo símbolos utilizando tablas de símbolos compilador, el análisis de dependencias de datos, etc. O puede simplemente deducir, que CallFunc(Name('IOError'), ...) "definitivamente debe significar elevar IOError", que está bastante bien para obtener resultados prácticos rápidos :)

+0

Gracias por esta interesante respuesta. Aunque no entendí por qué debería buscar algo más aparte de todos los nodos de aumento. ¿Por qué debería "resolver símbolos usando tablas de símbolos de compilación, analizando dependencias de datos"? ¿No es la única forma de elevar la excepción de raise()? – GabiMe

+0

Dado el valor 'v.nodes' anterior, no se puede decir realmente qué es 'Name ('IOError')' o 'Name ('e')'. No sabe a qué valor (s) pueden apuntar los 'IOError' y' e', ya que son las llamadas variables libres. Incluso si su contexto vinculante fuera conocido (aquí entran en juego las tablas de símbolos), debe realizar algún tipo de análisis de dependencia de datos para inferir sus valores exactos (esto debería ser difícil en Python). –

+0

Como está buscando una solución práctica semiautomatizada, una lista de '['IOError (errno.ENOENT," not found ")', 'e']' que se muestra al usuario está bien.Pero no puede inferir clases reales de valores de variables representadas por cadenas :) (lo siento por reenviar) –

23

Solo debe detectar las excepciones que manejará.

La captura de todas las excepciones por sus tipos concretos no tiene sentido. Debería detectar las excepciones específicas que puede y manejarán. Para otras excepciones, puede escribir un catch genérico que capte "base Exception", lo registre (use la función str()) y finalice su programa (o haga otra cosa que sea apropiada en una situación crashy).

Si realmente va a manejar todas las excepciones y estamos seguros de que ninguno de ellos son mortales (por ejemplo, si se está ejecutando el código en algún tipo de un entorno de espacio aislado), entonces su enfoque de la captura de BaseException genérica se adapte a su objetivos.

También te puede interesar language exception reference, no una referencia para la biblioteca que estás utilizando.

Si la referencia de la biblioteca es muy pobre y que no vuelva a lanzar sus propias excepciones al coger las de sistema, el único enfoque es útil para ejecutar pruebas (tal vez agregarlo al conjunto de pruebas, porque si algo es indocumentado , puede cambiar!). Elimine un archivo crucial para su código y verifique qué excepción se está lanzando. Suministre demasiados datos y compruebe qué error produce.

Tendrá que ejecutar pruebas de todos modos, ya que, incluso si existiera el método para obtener las excepciones por código fuente, no le daría ninguna idea de cómo debe manejar cualquiera de esos. Tal vez debería mostrar el mensaje de error "¡No se encuentra el archivo needful.txt!" cuando tomas IndexError? Solo la prueba puede decir.

+6

Claro, pero ¿cómo puede uno decidir qué excepciones debería manejar si no sabe lo que podría arrojarse? – GabiMe

+0

@ bugspy.net, corrigió mi respuesta para reflejar este asunto –

+0

Tal vez es hora de que el analizador de código fuente pueda averiguarlo. No debería ser demasiado difícil de desarrollar, creo que – GabiMe

1

Normalmente, debe detectar excepciones solo alrededor de unas pocas líneas de código. No querrá poner toda su función main en la cláusula try except. por cada pocas líneas que siempre debería (o puede verificar fácilmente) qué tipo de excepción podría surgir.

documentos tienen un exhaustivo list of built-in exceptions. no intente exceptuar aquellas excepciones que no espera, pueden ser manejadas/esperadas en el código de llamada.

editar: ¡lo que se arroje depende obviamente de lo que está haciendo! acceder al elemento aleatorio de una secuencia: IndexError, elemento aleatorio de un dict: KeyError, etc.

Simplemente intente ejecutar esas pocas líneas en IDLE y ocasione una excepción. Pero la prueba de unidad sería una mejor solución, naturalmente.

+0

Esto no responde mi pregunta simple. No pregunto cómo diseñar mi manejo de excepciones, ni cuándo ni cómo atraparlo. Pregunto cómo averiguar qué podría arrojarse – GabiMe

+1

@ bugspy.net: es ** imposible ** hacer lo que pide, y esta es una solución perfectamente válida. –

9

La herramienta correcta para resolver este problema es unittest. Si tiene excepciones planteadas por código real que los unittest no aumentan, entonces necesita más unittest.

consideran este

def f(duck): 
    try: 
     duck.quack() 
    except ??? could be anything 

pato puede ser cualquier objeto

Obviamente se puede tener un AttributeError si pato tiene ningún charlatán, un pato TypeError si tiene un charlatán, pero no es exigible. No tienes idea de lo que podría plantear duck.quack() sin embargo, tal vez incluso un DuckError o algo

Ahora supongamos que tiene un código como éste

arr[i] = get_something_from_database() 

Si se plantea una IndexError no se sabe si ha venido de arr [i] o desde el interior de la función de la base de datos. Por lo general, no importa tanto dónde ocurrió la excepción, sino que algo salió mal y no sucedió lo que quería que sucediera.

Una técnica útil es atrapar y tal vez volver a subir la excepción como esta

except Exception as e 
    #inspect e, decide what to do 
    raise 
+0

¿Por qué atraparlo si va a "volver a publicarlo"? –

+0

No tiene _ para volver a publicarlo, eso es lo que se suponía que el comentario indicaba. –

+1

También puede optar por registrar la excepción en algún lugar y luego resubir –

3

me encontré con esto cuando el uso de zócalo, que quería encontrar una Todas las condiciones de error en las que me encontraría (así que en vez de tratar de crear errores y averiguar qué socket solo quería una lista concisa). En última instancia terminé grep'ing "/usr/lib64/python2.4/test/test_socket.py" para "levantar":

$ grep raise test_socket.py 
Any exceptions raised by the clients during their tests 
     raise TypeError, "test_func must be a callable function" 
    raise NotImplementedError, "clientSetUp must be implemented." 
    def raise_error(*args, **kwargs): 
     raise socket.error 
    def raise_herror(*args, **kwargs): 
     raise socket.herror 
    def raise_gaierror(*args, **kwargs): 
     raise socket.gaierror 
    self.failUnlessRaises(socket.error, raise_error, 
    self.failUnlessRaises(socket.error, raise_herror, 
    self.failUnlessRaises(socket.error, raise_gaierror, 
     raise socket.error 
    # Check that setting it to an invalid value raises ValueError 
    # Check that setting it to an invalid type raises TypeError 
    def raise_timeout(*args, **kwargs): 
    self.failUnlessRaises(socket.timeout, raise_timeout, 
    def raise_timeout(*args, **kwargs): 
    self.failUnlessRaises(socket.timeout, raise_timeout, 

que es una lista bastante concisa de errores. Ahora, por supuesto, esto solo funciona caso por caso y depende de que las pruebas sean precisas (que generalmente son). De lo contrario, es necesario capturar todas las excepciones, registrarlas y diseccionarlas y descubrir cómo manejarlas (lo que con las pruebas unitarias no sería demasiado difícil).

+1

Esto fortalece mi argumento, que el manejo de excepciones en Python es muy problemático, si tenemos que usar grep o analizadores fuente para tratar con algo tan básico (que por ejemplo en Java existía desde el día Uno. A veces la verbosidad es algo bueno. Java es detallado, pero al menos no hay sorpresas desagradables. – GabiMe

+0

@GabiMe, No es que esta capacidad (o el tipado estático en general) sea una bala de plata para evitar todos los errores. Java es _full_ de sorpresas desagradables. Es por eso que el eclipse se bloquea regularmente. –

+0

@gnibber, claro, pero seguro que ayuda a prevenir muchos errores. – GabiMe

3

Nadie explicó hasta ahora, por qué no puede tener una lista completa y 100% correcta de excepciones, así que pensé que vale la pena comentar. Una de las razones es una función de primera clase. Digamos que usted tiene una función como esta:

def apl(f,arg): 
    return f(arg) 

ahora apl puede plantear cualquier excepción que plantea f. Si bien no hay muchas funciones como esa en la biblioteca principal, todo lo que utiliza la comprensión de listas con filtros personalizados, mapas, reducir, etc. se ve afectado.

La documentación y los analizadores de origen son las únicas fuentes de información "serias" aquí. Solo tenga en cuenta lo que no pueden hacer.

0

Hay dos formas en que encuentro informativo. El primero, ejecuta las instrucciones en iPython, que mostrará el tipo de excepción.

n = 2 
str = 'me ' 
str + 2 
TypeError: unsupported operand type(s) for +: 'int' and 'str' 

En la segunda forma, nos conformamos con atrapar demasiado y mejorarlo. Incluya una expresión try en su código y capture excepto Exception como err. Imprima datos suficientes para saber qué excepción se produjo. A medida que se lanzan excepciones, mejore su código agregando una cláusula except más precisa. Cuando sienta que ha guardado en caché todas las excepciones relevantes, elimine el todo incluido. Algo bueno que hacer de todos modos porque absorbe los errores de programación.

Cuestiones relacionadas