Elevar (lanzar) manualmente una excepción en Python

2266

¿Cómo puedo generar una excepción en Python para que luego pueda capturarse a través de un exceptbloque?

TIMEX
fuente

Respuestas:

2939

¿Cómo lanzo / levanto manualmente una excepción en Python?

Utilice el constructor de excepciones más específico que se ajuste semánticamente a su problema .

Sea específico en su mensaje, por ejemplo:

raise ValueError('A very specific bad thing happened.')

No plantees excepciones genéricas

Evita criar un genérico Exception. Para atraparlo, deberá atrapar todas las demás excepciones más específicas que lo subclasifiquen.

Problema 1: Ocultar errores

raise Exception('I know Python!') # Don't! If you catch, likely to hide bugs.

Por ejemplo:

def demo_bad_catch():
    try:
        raise ValueError('Represents a hidden bug, do not catch this')
        raise Exception('This is the exception you expect to handle')
    except Exception as error:
        print('Caught this error: ' + repr(error))

>>> demo_bad_catch()
Caught this error: ValueError('Represents a hidden bug, do not catch this',)

Problema 2: no atrapará

Y las capturas más específicas no atraparán la excepción general:

def demo_no_catch():
    try:
        raise Exception('general exceptions not caught by specific handling')
    except ValueError as e:
        print('we will not catch exception: Exception')


>>> demo_no_catch()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in demo_no_catch
Exception: general exceptions not caught by specific handling

Mejores prácticas: raisedeclaración

En su lugar, use el constructor de excepciones más específico que se ajuste semánticamente a su problema .

raise ValueError('A very specific bad thing happened')

que también permite fácilmente pasar un número arbitrario de argumentos al constructor:

raise ValueError('A very specific bad thing happened', 'foo', 'bar', 'baz') 

El argsatributo en el Exceptionobjeto accede a estos argumentos . Por ejemplo:

try:
    some_code_that_may_raise_our_value_error()
except ValueError as err:
    print(err.args)

huellas dactilares

('message', 'foo', 'bar', 'baz')    

En Python 2.5, messagese agregó un atributo real para BaseExceptionalentar a los usuarios a subclase Excepciones y dejar de usar args, pero la introducción messagey la desaprobación original de args se ha retraído .

Mejores prácticas: exceptcláusula

Cuando esté dentro de una cláusula except, es posible que desee, por ejemplo, registrar que ocurrió un tipo específico de error y luego volver a subir. La mejor manera de hacer esto mientras se preserva el seguimiento de la pila es usar una declaración de subida simple. Por ejemplo:

logger = logging.getLogger(__name__)

try:
    do_something_in_app_that_breaks_easily()
except AppError as error:
    logger.error(error)
    raise                 # just this!
    # raise AppError      # Don't do this, you'll lose the stack trace!

No modifiques tus errores ... pero si insistes.

Puede preservar el stacktrace (y el valor de error) con sys.exc_info(), pero esto es mucho más propenso a errores y tiene problemas de compatibilidad entre Python 2 y 3 , prefiera usar un desnudo raisepara volver a subir.

Para explicar: sys.exc_info()devuelve el tipo, el valor y el rastreo.

type, value, traceback = sys.exc_info()

Esta es la sintaxis en Python 2; tenga en cuenta que esto no es compatible con Python 3:

    raise AppError, error, sys.exc_info()[2] # avoid this.
    # Equivalently, as error *is* the second object:
    raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]

Si lo desea, puede modificar lo que sucede con su nuevo aumento, por ejemplo, establecer nuevo argspara la instancia:

def error():
    raise ValueError('oops!')

def catch_error_modify_message():
    try:
        error()
    except ValueError:
        error_type, error_instance, traceback = sys.exc_info()
        error_instance.args = (error_instance.args[0] + ' <modification>',)
        raise error_type, error_instance, traceback

Y hemos preservado todo el rastreo mientras modificamos los argumentos. Tenga en cuenta que esta no es una práctica recomendada y es una sintaxis no válida en Python 3 (por lo que es mucho más difícil evitar la compatibilidad).

>>> catch_error_modify_message()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in catch_error_modify_message
  File "<stdin>", line 2, in error
ValueError: oops! <modification>

En Python 3 :

    raise error.with_traceback(sys.exc_info()[2])

Nuevamente: evite manipular manualmente las trazas. Es menos eficiente y más propenso a errores. Y si está utilizando subprocesos e sys.exc_infoincluso puede obtener el rastreo incorrecto (especialmente si está utilizando el manejo de excepciones para el flujo de control, lo que personalmente tienden a evitar).

Python 3, encadenamiento de excepciones

En Python 3, puede encadenar Excepciones, que preservan las trazas:

    raise RuntimeError('specific message') from error

Se consciente:

  • esto no permite cambiar el tipo de error que se generan, y
  • Esto no es compatible con Python 2.

Métodos obsoletos:

Estos pueden esconderse fácilmente e incluso entrar en el código de producción. Desea generar una excepción, y al hacerlo, se generará una excepción, ¡ pero no la prevista!

Válido en Python 2, pero no en Python 3 es el siguiente:

raise ValueError, 'message' # Don't do this, it's deprecated!

Solo válido en versiones mucho más antiguas de Python (2.4 y versiones anteriores ), aún puede ver personas elevando cadenas:

raise 'message' # really really wrong. don't do this.

En todas las versiones modernas, esto en realidad aumentará a TypeError, porque no estás elevando un BaseExceptiontipo. Si no está buscando la excepción correcta y no tiene un revisor que conozca el problema, podría entrar en producción.

Ejemplo de uso

Levanto Excepciones para advertir a los consumidores de mi API si la están usando incorrectamente:

def api_func(foo):
    '''foo should be either 'baz' or 'bar'. returns something very useful.'''
    if foo not in _ALLOWED_ARGS:
        raise ValueError('{foo} wrong, use "baz" or "bar"'.format(foo=repr(foo)))

Cree sus propios tipos de error cuando sea apropiado

"Quiero cometer un error a propósito, para que entre en la excepción"

Puede crear sus propios tipos de error, si desea indicar que algo específico está mal con su aplicación, simplemente subclasifique el punto apropiado en la jerarquía de excepción:

class MyAppLookupError(LookupError):
    '''raise this when there's a lookup error for my app'''

y uso:

if important_key not in resource_dict and not ok_to_be_missing:
    raise MyAppLookupError('resource is missing, and that is not ok.')
Aaron Hall
fuente
19
Gracias por esto, es exactamente lo que necesitaba. Lo básico raisees lo que necesitaba para poder realizar una depuración de errores personalizada en múltiples niveles de ejecución de código sin romper el seguimiento de la pila.
CaffeineConnoisseur
Esta es una respuesta genial. Pero todavía trabajo con una gran cantidad de código 2.7, y a menudo me encuentro con ganas de agregar información a una excepción inesperada, como una posición del archivo de entrada o los valores de algunas variables, pero mantener la pila original y la excepción. Puedo registrarlo, pero a veces no quiero que se registre, por ejemplo, si el código principal finalmente lo maneja. raise sys.exc_info()[0], (sys.exc_info()[1], my_extra_info), sys.exc_info()[2]parece hacer lo que quiero, y nunca he tenido problemas con eso. Pero se siente hacky, y no es una práctica aceptada. ¿Hay una mejor manera?
Michael Scheper
2
@brennanyoung En ese contexto, creo que podría ser confuso generar un SyntaxError, probablemente debería plantear una excepción personalizada. Explico cómo hacerlo aquí: stackoverflow.com/a/26938914/541136
Aaron Hall
2
Tenga en cuenta que la cita completa es "Todas las excepciones integradas que no salen del sistema se derivan de esta clase. Todas las excepciones definidas por el usuario también deben derivarse de esta clase". - Eso significa principalmente que no debe usar una de las 4 excepciones que no se derivan de Exceptionsu clase principal: puede subclasificar algo más específico y debe hacerlo si tiene sentido.
Aaron Hall
1
En el ejemplo de " Mejores prácticas: excepto la cláusula ", utiliza una AppErrorexcepción indefinida . Puede ser mejor usar un error incorporado comoAttributeError
Stevoisiak
530

NO HAGAS ESTO . Criar a un desnudo no Exceptiones absolutamente lo correcto; vea la excelente respuesta de Aaron Hall en su lugar.

No puede ser mucho más pitónico que esto:

raise Exception("I know python!")

Consulte los documentos de la declaración de aumento para python si desea obtener más información.

Gabriel Hurley
fuente
67
¡No por favor! Esto elimina el potencial de ser específico sobre lo que atrapas. Es totalmente la forma incorrecta de hacerlo. Eche un vistazo a la excelente respuesta de Aaron Hall en lugar de esta. En momentos como este, desearía poder dar más de un voto negativo por respuesta.
Dawood ibn Kareem
27
@PeterR Es igualmente terrible que tenga tan pocos votos negativos. ¡A NADIE que lea esta respuesta, NO HAGA ESTO NUNCA! La respuesta correcta es la de Aaron Hall.
Dawood ibn Kareem
66
Creo que debería haber una explicación más detallada de por qué esto está mal o es tan malo.
Charlie Parker
99
@CharlieParker Hay. Es la primera parte de la respuesta de Aaron Hall .
Dinei
55
¿Por qué no se puede marcar esta respuesta para su eliminación? ¡Ya tiene 93 votos a favor!
codeforester
55

En Python3 hay 4 sintaxis diferentes para excepciones de rasing:

1. raise exception 
2. raise exception (args) 
3. raise
4. raise exception (args) from original_exception

1. aumento de excepción vs. 2. aumento de excepción (argumentos)

Si utiliza raise exception (args) para generar una excepción args, se imprimirá cuando imprima el objeto de excepción, como se muestra en el siguiente ejemplo.

  #raise exception (args)
    try:
        raise ValueError("I have raised an Exception")
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error I have raised an Exception 



  #raise execption 
    try:
        raise ValueError
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error 

3 elevar

raisedeclaración sin ningún argumento vuelve a plantear la última excepción. Esto es útil si necesita realizar algunas acciones después de detectar la excepción y luego desea volver a subirla. Pero si no hubo ninguna excepción antes, la raisedeclaración genera TypeErrorExcepción.

def somefunction():
    print("some cleaning")

a=10
b=0 
result=None

try:
    result=a/b
    print(result)

except Exception:            #Output ->
    somefunction()           #some cleaning
    raise                    #Traceback (most recent call last):
                             #File "python", line 8, in <module>
                             #ZeroDivisionError: division by zero

4. aumentar la excepción (args) de original_exception

Esta declaración se utiliza para crear un encadenamiento de excepciones en el que una excepción que se genera en respuesta a otra excepción puede contener los detalles de la excepción original, como se muestra en el ejemplo a continuación.

class MyCustomException(Exception):
pass

a=10
b=0 
reuslt=None
try:
    try:
        result=a/b

    except ZeroDivisionError as exp:
        print("ZeroDivisionError -- ",exp)
        raise MyCustomException("Zero Division ") from exp

except MyCustomException as exp:
        print("MyException",exp)
        print(exp.__cause__)

Salida:

ZeroDivisionError --  division by zero
MyException Zero Division 
division by zero
N Randhawa
fuente
77
Tenga en cuenta que PEP8 prefiere exception(args)másexception (args)
Gloweye
También hay raise exception(args) from Noneque decir que la excepción actualmente activa se manejó y ya no es de interés. De lo contrario, si genera una excepción dentro de un exceptbloque y no se maneja, los rastreos para ambas excepciones se mostrarán separados por el mensaje "Durante el manejo de la excepción anterior, se produjo otra excepción"
cg909
35

Para el caso común en el que necesita lanzar una excepción en respuesta a algunas condiciones inesperadas, y que nunca tiene la intención de atrapar, sino simplemente fallar rápidamente para permitirle depurar desde allí si alguna vez sucede, la más lógica parece ser AssertionError:

if 0 < distance <= RADIUS:
    #Do something.
elif RADIUS < distance:
    #Do something.
else:
    raise AssertionError("Unexpected value of 'distance'!", distance)
Evgeni Sergeev
fuente
19
Este es un mejor caso ValueErrorque AssertionErrorporque no hay problema con una afirmación (porque no se está haciendo ninguna aquí), el problema es con un valor. Si realmente quieres un AssertionErroren este caso, escribe assert distance > 0, 'Distance must be positive'. Pero no debe verificar los errores de esa manera porque las aserciones se pueden desactivar ( python -O).
Two-Bit Alchemist
1
@ Two-BitAlchemist Buen punto. La idea se perdió en la simplificación, cuando escribí el simple ejemplo anterior. En muchos casos similares es una condición que no está asociada con un valor particular. Más bien, el significado es "el flujo de control nunca debería llegar aquí".
Evgeni Sergeev
2
@ Las afirmaciones de Alquimista de dos bits se pueden desactivar, sí, pero ¿no debería usarlas para la verificación de errores?
Evgeni Sergeev
Bueno, eso depende. No permitiría que ese sea mi único error al registrar un programa que pretendía distribuir. Por otro lado, podría hacer un programa solo para mis compañeros de trabajo y decirles que lo usan bajo su propio riesgo si lo ejecutan -O.
Alquimista de dos bits
1
@ Two-BitAlchemist Para mí, el papel de las aserciones no es la verificación de errores per se (que es para lo que sirven las pruebas), sino que establecen barreras dentro del código que ciertos errores no pueden superar. Por lo tanto, se hace más fácil localizar y aislar los errores, lo que inevitablemente ocurrirá. Estos son solo buenos hábitos que requieren poco esfuerzo, mientras que las pruebas requieren mucho esfuerzo y mucho tiempo.
Evgeni Sergeev
12

Lea las respuestas existentes primero, esto es solo un apéndice.

Tenga en cuenta que puede generar excepciones con o sin argumentos.

Ejemplo:

raise SystemExit

sale del programa, pero es posible que desee saber qué sucedió, por lo que puede usar esto.

raise SystemExit("program exited")

esto imprimirá "programa salido" en stderr antes de cerrar el programa.

Anant Prakash
fuente
2
¿No es esto en contra del paradigma OOP? Supongo que el primer caso arroja la referencia de clase y el segundo una instancia de SystemExit. ¿No raise SystemExit()sería la mejor opción? ¿Por qué el primero incluso funciona?
burny 01 de
2

Otra forma de lanzar una excepción es assert. Puede usar afirmar para verificar que se está cumpliendo una condición, de lo contrario, aumentará AssertionError. Para más detalles echa un vistazo aquí .

def avg(marks):
    assert len(marks) != 0,"List is empty."
    return sum(marks)/len(marks)

mark2 = [55,88,78,90,79]
print("Average of mark2:",avg(mark2))

mark1 = []
print("Average of mark1:",avg(mark1))
Rehan Haider
fuente
2

Solo para tener en cuenta: hay momentos en los que NO desea manejar excepciones genéricas. Si está procesando un montón de archivos y registrando sus errores, es posible que desee detectar cualquier error que ocurra para un archivo, registrarlo y continuar procesando el resto de los archivos. En ese caso, un

try:
    foo() 
except Exception as e:
    print(str(e)) # Print out handled error

bloquear una buena forma de hacerlo. Sin embargo, aún querrás raiseexcepciones específicas para que sepas lo que significan.

markemus
fuente
0

Deberías aprender la declaración raise de python para eso. Debe mantenerse dentro del bloque de prueba. Ejemplo

try:
    raise TypeError            #remove TypeError by any other error if you want
except TypeError:
    print('TypeError raised')
Abhijeet.py
fuente