12 - Curso de Python - Try Exception
Cualquier duda suelo atender en este foro de python
Todo el código de este curso de Python gratuito está en github https://github.com/chuidiang/chuidiang-ejemplos/tree/master/PYTHON/curso-python. En línea comandos python tienes cómo arrancar la consola de comandos de python por si quieres ir probando los ejemplos de manejo de excepciones (try except) en Python.
Anterior: 11 - Curso de Python - Bases de datos -- Índice: Curso de Python -- Siguiente: 13 - Curso de Python - Leer y escribir ficheros en python.
Qué son excepciones en Python[edit]
Excepciones en Python son errores que se producen durante la ejecución de nuestro código por una situación ineseperada. Por ejemplo, si haces una divisíón por cero obtienes una excepción
>>> 1/0 Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: division by zero
Este es un ejemplo muy directo. Al dividir por cero obtenemos una excepción ZeroDivisionError
. Nadie va a escribir ese código. Pero sí puede pasar si haces un progama que lea los datos de algún sitio o pida datos al usuario. Por ejemplo.
>>> a = input ('Introduce un numero ') Introduce un numero abc >>> print ('Tu numero mas 1 es ', int(a)+1) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: 'abc'
El usuario ha sido un poco "malo" y cuando le hemos pedido un número, ha metido tres letras. Nuestro programa pretendía sacar ese número más uno. Pero ha dado una excepción ValueError
.
Si ocurre un error y no hacemos nada para tratarlo, el programa termina mostrando el error. Por ello, es interesante capturar y tratar los posibles errores que se produzcan, de forma que nuestro programa pueda seguir funcionando o al menos, no termine de forma brusca. Veamos como hacerlo en los siguientes apartados.
Manejo de las excpeciones[edit]
Python tiene la sentencia try except else finally
para tratar este tipo de errores inesperados. El esqueleto completo de esta sentencia es
try: sentencias1 except: sentencias2 else: sentencias3 finally: sentencias4
sentencias1 es el bloque de código donde prevemos que puede haber este error inesperado. Es decir, código donde hagamos operaciones matemáticas susceptibles, por ejemplo, de divisiones por cero. Código donde se convierten cadenas de texto a enteros. Códido donde se abre y lee un fichero que puede fallar porque el fichero no exista o no tengamos permisos para leerlo. Y un largo etcétera de posibles trozos de código donde puede haber errores inesperados, pero que sabemos que son posibles.
Si durante la ejecución de sentencias1 se produce un error, dejan de ejecutarse las que queden y se va inmediatamente al bloque except
. Ahí podemos intentar arreglar el error, parar la operación y sacar un mensaje de error al usuario, etc. Lo que consideremos en cada caso que pueda ayudarnos a salir airosos de una situación de error inesperado.
Si en sentencias1 no se produce ningún error, entonces se ejeucta el bloque else
. Este bloque es opcional, puede no estar. Sería como un conjunto de sentencias adicionales que queremos ejecutar si sentencias1 ha ido bien y, por supuesto, no prevemos que sentencias3 pueda dar algún error. Como buena costumbre de programación, en el bloque try
debemos poner solo aquellas sentencias susceptibles de fallo. El resto de código que hagamos que no sea susceptible de fallo, debe ir en el else
. Por ejemplo, si queremos hacer una división y sacar el resultado en pantalla, en el try
hacemos la división. Una vez hecha sin error, en el bloque else
sacamos el resultado por pantalla.
Y tanto si sentencias1 da error como si no, el bloque finally
se ejecutará siempre al final. Este bloque es útil para cerrar recursos que hayamos abierto en sentencias1. Por ejemplo, imagina que en sentencias1 abrimos un fichero para escribir datos en él. Escribimos datos y como última sentencia1 cerramos el fichero. Algo así
try: abrir fichero escribir datos en el fichero cerrar el fichero ...
¿Cual es el problema de esto?. Imagina que abrimos el fichero pero salta una excpeción al escribir datos. El cierre del fichero ya no se ejecutaría. Por ello, es buena idea llevárselo al finally
, así
try: abrir fichero escribir datos en el fichero except: ha habido algún error al abrir/escribir en fichero finally: cerrar fichero si está abierto.
Esta es la forma más segura de no dejar recursos abiertos. Quien dice ficheros, dice conexiones a base de datos, conexiones de red y en general, cualquier recuros que se abre para trabajar con él y se cierra al acabar.
Capturar excepciones concretas[edit]
Hemos comentado que except
se ejecuta cuando hay algún error y que else
se ejecuta si no se produce ningún error. Esto es cierto con lo que hemos puesto hasta ahora, pero hay más posibilidades. Si un bloque try
puede producir varios tipos de errores, por ejemplo, dos de los que ya hemos visto ZeroDivisionError
y ValueError
, podemos poner tantos except
como tipos de error distintos queramos capturar. Por ejemplo
try: sentencias1 except ZeroDivisionError as error_division: sentencias2 excpet ValueError as value_error: sentencias3 else: sentencias4
La parte as variable
es opcional. Solo la necesitamos si queremos tener el error como una variable para sacar algo específico de él en pantalla. Si sentencias1 producde uno de esos dos errores, el código irá al except
adecuado. Pero si produce un error distinto, no se capturará, pero tampoco se ejecutará el else
. Podemos poner un último except
justo antes de else
. Este except
capturará cualquier excepción no contemplada en los casos anteriores. Por ejemplo
>>> try: ... print(1/0) ... except ValueError: ... print('Un ValueError') ... except Exception as error: ... print('Un error de tipo ',type(error)) ... else: ... print('Sin errores') ... Un error de tipo <class 'ZeroDivisionError'>
Hemos hecho una división por cero en el bloque try
. Capturamos la excepción ValueException
y luego otra general, de tipo Excepction
, que metemos en la variable error
. En este bloque sacamos por pantalla el tipo de excepción que es. Ponemos un bloque else
aunque no se ejecutará, puesto que en el bloque try
estamos haciendo una división por cero que producirá una excepción. Vemos que el resultado al ejecutar esto es que se ha producido una excepción de tipo ZeroDivisionError
.
Si queremos capturar varias excepciones concretas y con todas ellas hacer lo mismo, se admite esta sintaxis
>>> try: ... print(1/0) ... except (ValueError, ZeroDivisionError) as error: ... print('Error de tipo ',type(error)) ... Error de tipo <class 'ZeroDivisionError'>
Podemos poner entre paréntesis, detrás de except
, todas las excepciones que queramos.
Cadena de excepciones[edit]
Cuando capturamos una excepción, podemos tratarla, sacarla por pantalla o hacer lo que considermos. Pero también podemos después de tratarla, relanzarla para que la trate también alguien más. Como ejemplo, metemos el siguiente código en un fichero "division.py".
def divide(dividendo,divisor):
try:
cociente = dividendo/divisor
except ZeroDivisionError as error:
print ('Division por cero ', str(error))
raise
else:
return cociente
try:
resultado = divide(1,0)
except Exception as error:
print ('Algo ha ido mal', str(error))
else:
print ('1/0 es ', resultado)
Hacemos una función que divide dos números que le pasan. Ponemos el try
y toda la parafernalia except
y else
. Unicamente fíjate que en el except
hemos puesto al final un raise
, sin parámetros. Esto hace que aunque tratemos la excepción, esta siga su curso como si no la hubieramos tratado. Por lo que el trozo de código que hay a continuación, cuando llame a divide(1,0)
, recibirá también el error y podrá tratarlo también. Podemos ejecutar esto desde línea de comandos de windows/terminal linux y obtendremos:
C:\> python Division por cero division by zero Algo ha ido mal division by zero
Vemos los dos print
de ambos except
. Es decir, los dos han podido tratar la excepción.
Pero para poder tener más trazabilidad, podemos poner una sintaxis como raise una_excepcion from otra_excepcion
. Me explico. Ampliamos un poco el código
import traceback
class ApplicationError(Exception):
pass
def divide(dividendo,divisor):
try:
cociente = dividendo/divisor
except ZeroDivisionError as error:
raise ApplicationError('funcion divide()') from error
else:
return cociente
try:
resultado = divide(1,0)
except Exception as error:
traceback.print_exc()
else:
print ('1/0 es ', resultado)
El código es prácticamente igual salvo los siguientes cambios:
- importamos el módulo traceback. Esto nos permite sacar por pantalla toda la cadena de excepciones que han ido saltando.
- Hemos creao una excpecion propia de nuestra aplicación:
ApplicationError
. No te preocupes ahora por la sintaxis, vemos los detalles de cómo crear excepciones propias en el siguiente apartado. Aquí quédate sólo con la idea de que le hemos dicho a Python que hay una excpeción nueva que se llamaApplicationError
- La función
divide
no saca error por pantalla enexcept
. Unicamente y aquí uno de los puntos importantes del ejemplo, lanza unaApplictionError
"from" el error que se ha producido, es decir, laZeroDivisionErro
. Esto deja registrado internamente que la excepciónApplicationError
se ha producido por unaZeroDivisionError
. - En el código que usa la división, en el bloque
except
, ponemos una llamada atraceback.print_exc()
. Esta llamda, dentro del bloqueexcept
saca por pantalla la excepción que se ha producido con toda la cadena de excepciones que lo ha provocado. Veamos la salida de este programa para ver qué queremos decir.
C:\> python division.py Traceback (most recent call last): File "C:\Users\fjabe\Proyectos\chuidiang-ejemplos\PYTHON\curso-python\12-Try-Exception\division.py", line 8, in divide cociente = dividendo/divisor ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "C:\Users\fjabe\Proyectos\chuidiang-ejemplos\PYTHON\curso-python\12-Try-Exception\division.py", line 15, in <module> resultado = divide(1,0) File "C:\Users\fjabe\Proyectos\chuidiang-ejemplos\PYTHON\curso-python\12-Try-Exception\division.py", line 10, in divide raise ApplicationError('funcion divide()') from error ApplicationError: funcion divide()
Bueno, es una salida un poco engorrosa, pero leyendo de arriba a abajo y fijándose en las líneas de interés:
- Una
ZeroDivisionError
en la línea 8 del fichero division.py, encociente = dividendo/divisor
. Esta es la causa origen del error. - Ese error ha provocado después una
ApplicationError
en la línea 15 de division.py, enresultado = divide(1,0)
. - Y este error ha sido lanzado desde la línea 10 de divide.py, en
raise ApplicationError('funcion divide()') from error
.
Es decir, con este log en pantalla y usando en código cosas como raise una_excepcion from otra_excepcion
, podemos seguir todo el rastro del error desde su origen hasta el sitio que ha sido sacado por pantalla. Esta es muy útil para depurar cuando hay errores que no tenemos totalmente controlados o inesperados.
Excepciones propias de la aplicación[edit]
En nuestro programa tenemos la posiblidad de crear excepciones propias de nuestra aplicación para lanzarlas cuando necesitemos. Imagina que alguien introduce un usuario y password y cuando tu aplicación lo verifica ve que no es correcto. Podemos crear una excepción propia para esta circunstancia y luego lanzarla cuando corresponda. Para crearla, basta hacer una clase que herede de Exception
>>> class WrongUserPassword(Exception): ... pass ... >>> try: ... # Verificar usuario password. ... # Si es incorrecto, lanzamos la excepcion. ... raise WrongUserPassword() ... except WrongUserPassword: ... print('Usuario/Password no valido') ... Usuario/Password no valido
Hemos creado la clase WrongUserPassord
que hereda de Exception
. No queremos liarnos rellenando código de esa excpeción ahora, así que la dejamos vacía poniendo la sentencia pass
que no hace nada, pero sirve para ponerla donde obligatoriamente tengamos que meter algo.
Luego, en un try
, no hemos verificado usuario/password para no complicar el ejemplo, solo hemos puesto un comentario donde iría esa verificación. Si no el usuario y password no es válido, lanzamos nuestra excepción con raise WrongUserPassword()
.
Podríamos añadir información adicional a la hora de lanzar la excpeción. De hecho, la clase Excepcion
admite en el constructor N parámetros que se guarda internamente. Fijate en esto
>>> error = Exception('hola','soy',1,'excepcion') >>> str(error) "('hola', 'soy', 1, 'excepcion')"
Hemos creado una Exception
pasando cuatro parámetros cualesquiera. Si llamamos a str()
pasando la excepción, saca los parámatros por pantalla. Asi que si heredamos de Excepcion
, tenemos esto de regalo en nuestra excepción. En nuestro ejemplo de WrongUserPassword
, sin tocar la clase ya creada, podemos hacer
>>> import datetime >>> error = WrongUserPassword('Juan', datetime.datetime.now()) >>> str(error) "('Juan', datetime.datetime(2022, 8, 6, 12, 34, 22, 22338))"
Lo que nos permite guardar parámetros de interés en cualquier excpeción nuestra. En este ejemplo, hemos puesto el nombre de usuario y la fecha/hora del intento fallido. Por supuesto, podemos al definir nuestra excepción propia definir constructores o lo que necesitemos, aunque suele ser buena costumbre de programación dejar las clases de excepción lo más sencillas posibles.
Arbol de herencia de excepciones[edit]
Phython ya tiene un arbol de herencias de excepciones. La primera es BaseException
y de ahí van heredando las demás. Suele haber una excpecion padre por cada grupo relacionado de excepciones y luego ya las excepciones concretas de ese grupo. La siguiente tabla muestra el árbol de herencia de las excepciones en python
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError | +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- EncodingWarning +-- ResourceWarning
Python aconseja que las excepciones propias de nuestra aplicación las heredemos a partir de Excepcion
, como hemos hecho. Solo como ejemplo de jerarquía, fíjate por ejemplo que hay una excepción ArithmeticError
y debajo de ella cuelgan algunas concretas, como la ZeroDivisionError
que hemos estado usando hasta ahora.
Buenas constumbres a la hora de crear excepciones propias en python[edit]
Así que si quieres crear tus propias excepciones, suelen ser buenas costumbres:
- Heredando de
Excepcion
, pensar tu jerarquía propia de excepciones, o bien extender las ya existentes. Si haces un programa de contabilidad, quizás puedas hacer tu excepción padreContabilidadError
que herede deException
y luego ya excepciones concretas como podrían serSaldoInsuficienteError
,TransferenciaError
, etc, etc. - Suele terminarse el nombre de la excepción en "Error", así es fácilmente reconocible que la clase es una excepción. O Como ves en el árbol de Python, "Warning" si no son excepciones graves.
- Tus excpeciones convienen que estén en un módulo (fichero .py) propio de excepciones, por ejemplo,
errores_contabilidad.py
o algo así. El motivo es que las excepciones seguramente se usen a lo largo de todo tu programa. Si mezclas tus excepciones en módulos de tu programa que tienen otras clases y funciones que hacen otras cosas, tendrás que importar todo aunque sólo quieras usar la excepción. Aparte, que tendrás que recordar en qué módulo estaba definida.
Anterior: 11 - Curso de Python - Bases de datos -- Índice: Curso de Python -- Siguiente: 13 - Curso de Python - Leer y escribir ficheros en python.