En este tutorial, aprenderá a manejar las condiciones de error en Python desde un punto de vista completo del sistema. El manejo de errores es un aspecto crítico del diseño, y se extiende desde los niveles más bajos (a veces el hardware) hasta los usuarios finales. Si no tiene una estrategia consistente, su sistema no será confiable, la experiencia del usuario será deficiente y tendrá muchos desafíos en la depuración y la solución de problemas..
La clave del éxito es ser consciente de todos estos aspectos interconectados, considerarlos explícitamente y formar una solución que aborde cada punto..
Hay dos modelos principales de manejo de errores: códigos de estado y excepciones. Los códigos de estado pueden ser utilizados por cualquier lenguaje de programación. Las excepciones requieren soporte de idioma / tiempo de ejecución.
Python soporta excepciones. Python y su biblioteca estándar utilizan excepciones generosamente para informar sobre muchas situaciones excepcionales como los errores de IO, la división por cero, la indexación fuera de límites y también algunas situaciones no tan excepcionales como el final de la iteración (aunque está oculto). La mayoría de las bibliotecas siguen el ejemplo y plantean excepciones..
Eso significa que su código tendrá que manejar las excepciones generadas por Python y las bibliotecas de todos modos, por lo que también puede generar excepciones a su código cuando sea necesario y no confiar en los códigos de estado.
Antes de sumergirse en el santuario interno de Python, las excepciones y las mejores prácticas de manejo de errores, veamos algunas excepciones en la acción:
def f (): return 4/0 def g (): raise Exception ("No nos llame. Le llamaremos") def h (): try: f () excepto Exception como e: print (e) intente: g () excepto Excepción como e: imprimir (e)
Aquí está la salida al llamar h ()
:
h () división por cero No nos llames. Te llamaremos
Las excepciones de Python son objetos organizados en una jerarquía de clases.
Aquí está toda la jerarquía:
BaseException + - SystemExit + - KeyboardInterrupt + - GeneratorExit + - Exception + - StopIteration + - StandardError | + - BufferError | + - ArithmeticError | | + - FloatingPointError | | + - OverflowError | | + - ZeroDivisionError | + - AssertionError | + - AttributeError | + - EnvironmentError | | + - IOError | | + - OSError | | + - WindowsError (Windows) | | + - VMSError (VMS) | + - EOFError | + - ImportError | + - LookupError | | + - IndexError | | + - KeyError | + - MemoryError | + - NameError | | + - UnboundLocalError | + - ReferenceError | + - RuntimeError | | + - NotImplementedError | + - SyntaxError | | + - IndentationError | | + - TabError | + - SystemError | + - TypeError | + - ValueError | + - UnicodeError | + - UnicodeDecodeError | + - UnicodeEncodeError | + - UnicodeTranslateError + - Warning + - DeprecationWarning + - PendingDeprecationWarning + - RuntimeWarning + - SyntaxWarning + - UserWarning + - FutureWarning + - ImportWarning + - UnicodeWarning + - BytesWarning
Hay varias excepciones especiales que se derivan directamente de BaseException
, me gusta SystemExit
, Teclado interrumpir
y GeneratorExit
. Luego está la Excepción
clase, que es la clase base para StopIteration
, Error estándar
y Advertencia
. Todos los errores estándar se derivan de Error estándar
.
Cuando genera una excepción o alguna función a la que llama provoca una excepción, ese flujo de código normal termina y la excepción comienza a propagar la pila de llamadas hasta que encuentra un controlador de excepciones adecuado. Si no hay un controlador de excepciones disponible para manejarlo, el proceso (o más precisamente el subproceso actual) terminará con un mensaje de excepción no manejado.
Subir excepciones es muy fácil. Solo usas el aumento
palabra clave para elevar un objeto que es una subclase de la Excepción
clase. Podría ser una instancia de Excepción
En sí, una de las excepciones estándar (por ejemplo,. Error de tiempo de ejecución
), o una subclase de Excepción
Te derivaste a ti mismo. Aquí hay un pequeño fragmento de código que demuestra todos los casos:
# Levante una instancia de la clase Exception. Aumente Exception ('Ummm ... algo está mal') # Levante una instancia de la clase RuntimeError la excepción se creó a partir de la clase de fecha y hora de dattime SuperError (Exception): def __init __ (self, message): Exception .__ init __ (message) self.when = datetime.now () sube SuperError ('Ummm ... algo está mal')
Se detectan excepciones con el excepto
Cláusula, como viste en el ejemplo. Cuando detectas una excepción, tienes tres opciones:
Debe tragar la excepción si sabe cómo manejarlo y puede recuperarse por completo..
Por ejemplo, si recibe un archivo de entrada que puede estar en diferentes formatos (JSON, YAML), puede intentar analizarlo utilizando diferentes analizadores. Si el analizador JSON generó una excepción de que el archivo no es un archivo JSON válido, tráguelo e intente con el analizador YAML. Si el analizador YAML falló también, dejas que la excepción se propague.
importar json importar yaml def parse_file (nombre de archivo): intentar: devolver json.load (abrir (nombre de archivo)) excepto json.JSONDecodeError devolver yaml.load (abrir (nombre de archivo))
Tenga en cuenta que otras excepciones (por ejemplo, el archivo no encontrado o los permisos de no lectura) se propagarán y no serán detectados por la cláusula de excepción específica. Esta es una buena política en este caso en el que desea probar el análisis de YAML solo si el análisis de JSON falló debido a un problema de codificación de JSON.
Si quieres manejar todos excepciones entonces solo usa excepto excepción
. Por ejemplo:
def print_exception_type (func, * args, ** kwargs): try: return func (* args, ** kwargs) excepto Exception como e: tipo de impresión (e)
Tenga en cuenta que al agregar como e
, enlaza el objeto de excepción al nombre mi
disponible en su cláusula de excepción.
Para volver a subir, simplemente añada aumento
sin argumentos dentro de su controlador. Esto le permite realizar un manejo local, pero también permite que los niveles superiores lo hagan también. Aquí el invoke_function ()
La función imprime el tipo de excepción en la consola y luego vuelve a elevar la excepción..
def invoke_function (func, * args, ** kwargs): try: return func (* args, ** kwargs) excepto Exception como e: print type (e) raise
Hay varios casos en los que desearía plantear una excepción diferente. A veces desea agrupar varias excepciones de bajo nivel diferentes en una sola categoría que se maneja de manera uniforme mediante un código de nivel superior. En casos de orden, necesita transformar la excepción al nivel de usuario y proporcionar algún contexto específico de la aplicación.
A veces desea asegurarse de que se ejecute algún código de limpieza incluso si se generó una excepción en algún momento. Por ejemplo, puede tener una conexión de base de datos que desea cerrar una vez que haya terminado. Aquí está la manera incorrecta de hacerlo:
def fetch_some_data (): db = open_db_connection () consulta (db) close_db_Connection (db)
Si el consulta()
función plantea una excepción, entonces la llamada a close_db_connection ()
nunca se ejecutará y la conexión DB permanecerá abierta. los finalmente
La cláusula siempre se ejecuta después de que se ejecute un controlador de excepciones. Aquí está cómo hacerlo correctamente:
def fetch_some_data (): db = None try: db = open_db_connection () consulta (db) finalmente: si db no es None: close_db_connection (db)
La llamada a open_db_connection ()
No puede devolver una conexión o provocar una excepción. En este caso no es necesario cerrar la conexión DB..
Cuando usas finalmente
, tienes que tener cuidado de no generar excepciones allí porque enmascararán la excepción original.
Los administradores de contexto proporcionan otro mecanismo para envolver recursos como archivos o conexiones de base de datos en un código de limpieza que se ejecuta automáticamente incluso cuando se han generado excepciones. En lugar de bloques try-finally, usas el con
declaración. Aquí hay un ejemplo con un archivo:
def process_file (nombre de archivo): con abrir (nombre de archivo) como f: process (f.read ())
Ahora, incluso si proceso()
provocó una excepción, el archivo se cerrará correctamente de inmediato cuando el alcance de la con
se sale del bloque, independientemente de si la excepción se manejó o no.
El registro es prácticamente un requisito en sistemas no triviales y de larga ejecución. Es especialmente útil en aplicaciones web donde puede tratar todas las excepciones de forma genérica: simplemente registre la excepción y devuelva un mensaje de error a la persona que llama..
Cuando se registra, es útil registrar el tipo de excepción, el mensaje de error y el seguimiento de pila. Toda esta información está disponible a través del sys.exc_info
objeto, pero si usas el logger.exception ()
En su controlador de excepciones, el sistema de registro de Python extraerá toda la información relevante para usted..
Esta es la mejor práctica que recomiendo:
importar logging logger = logging.getLogger () def f (): try: flaky_func () excepto Exception: logger.exception () raise
Si sigue este patrón, entonces (suponiendo que configure el registro correctamente) no importa lo que pase, tendrá un registro bastante bueno en sus registros de lo que salió mal y podrá solucionar el problema..
Si vuelve a aumentar, asegúrese de no registrar la misma excepción una y otra vez en diferentes niveles. Es un desperdicio, y podría confundirlo y hacerle pensar que se produjeron varias instancias del mismo problema, cuando en la práctica se registró una sola instancia varias veces.
La forma más sencilla de hacerlo es dejar que todas las excepciones se propaguen (a menos que puedan manejarse con confianza y ser tragadas antes) y luego hacer el registro cerca del nivel superior de su aplicación / sistema..
El registro es una capacidad. La implementación más común es el uso de archivos de registro. Pero, para sistemas distribuidos a gran escala con cientos, miles o más servidores, esta no es siempre la mejor solución.
Para hacer un seguimiento de las excepciones en toda su infraestructura, un servicio como centinela es muy útil. Centraliza todos los informes de excepciones y, además del apilamiento, agrega el estado de cada marco de pila (el valor de las variables en el momento en que se generó la excepción). También proporciona una interfaz realmente agradable con paneles, informes y formas de desglosar los mensajes por múltiples proyectos. Es de código abierto, por lo que puede ejecutar su propio servidor o suscribirse a la versión alojada..
Algunos fallos son temporales, en particular cuando se trata de sistemas distribuidos. Un sistema que se asusta a la primera señal de problemas no es muy útil.
Si su código está accediendo a algún sistema remoto que no responde, la solución tradicional son los tiempos de espera, pero a veces no todos los sistemas están diseñados con tiempos de espera. Los tiempos de espera no siempre son fáciles de calibrar a medida que cambian las condiciones.
Otro enfoque es fallar rápido y luego volver a intentarlo. El beneficio es que si el objetivo responde rápidamente, no tiene que pasar mucho tiempo en la condición de sueño y puede reaccionar de inmediato. Pero si falló, puede volver a intentarlo varias veces hasta que decida que es realmente inalcanzable y generar una excepción. En la siguiente sección, presentaré un decorador que puede hacerlo por ti..
Dos decoradores que pueden ayudar con el manejo de errores son los @log_error
, que registra una excepción y luego la vuelve a elevar, y la @procesar de nuevo
decorador, que volverá a intentar llamar a una función varias veces.
Aquí hay una implementación simple. El decorador exceptúa un objeto logger. Cuando decora una función y se invoca la función, envolverá la llamada en una cláusula try-except, y si hubiera una excepción, la registrará y finalmente volverá a subir la excepción..
def log_error (logger) def decorado (f): @ functools.wraps (f) def envuelto (* args, ** kwargs): try: return f (* args, ** kwargs) excepto Exception como e: if logger: logger .excepción (e) elevar retorno envuelto retorno decorado
Aquí está cómo usarlo:
importar logging logger = logging.getLogger () @log_error (logger) def f (): aumentar Excepción ('Soy excepcional')
Aquí hay una muy buena implementación del decorador @retry..
tiempo de importación importar matemática # Reintentar decorador con retroceso exponencial def reintento (intentos, retardo = 3, retroceso = 2): "reintenta una función o método hasta que devuelve Verdadero. El retardo establece el retardo inicial en segundos, y el retroceso establece el factor por el el retraso debe prolongarse después de cada falla. El retroceso debe ser mayor que 1, o de lo contrario no es realmente un retroceso. Los intentos deben ser al menos 0 y el retraso mayor que 0. "si el retroceso <= 1: raise ValueError("backoff must be greater than 1") tries = math.floor(tries) if tries < 0: raise ValueError("tries must be 0 or greater") if delay <= 0: raise ValueError("delay must be greater than 0") def deco_retry(f): def f_retry(*args, **kwargs): mtries, mdelay = tries, delay # make mutable rv = f(*args, **kwargs) # first attempt while mtries > 0: si rv es Verdadero: # Hecho en retorno exitoso mtries Verdaderos - = 1 # consume un intento time.sleep (mdelay) # espera ... mdelay * = retroceso # hace que el futuro espere más tiempo rv = f (* args, ** kwargs) # Intentar nuevamente devolver Falso # Se agotó el intento :-( devolver f_retry # verdadero decorador -> función decorada devolver deco_retry # @retry (arg [, ...]) -> verdadero decorador
El manejo de errores es crucial tanto para los usuarios como para los desarrolladores. Python proporciona una gran compatibilidad en el idioma y en la biblioteca estándar para el manejo de errores basado en excepciones. Al seguir diligentemente las mejores prácticas, puede conquistar este aspecto a menudo descuidado.
Aprende Python con nuestra completa guía de tutoriales de Python, ya sea que estés empezando o seas un programador experimentado que busca aprender nuevas habilidades..