Saltar a contenido

Manejo de errores en Python

Recordemos nuestro programa y lo que ocurre cuando el usuario ingresa un valor que no es un número entero:

python
# Solicitar al usuario un número entero
# input siempre devuelve una cadena de caracteres.
# Por lo tanto es necesario convertir la cadena a un número entero con la función int()
numero = int(input("Ingrese un número entero: "))

# Imprimir el número ingresado utilizando f-strings
print(f"El número ingresado es: {numero}")

Al ejecutar el programa y escribir un texto en lugar de un número entero, el programa se detiene y muestra un mensaje de error:

Terminal (Entrada/Salida)
Ingrese un número entero: hola
Traceback (most recent call last):
File "c:\Usuarios\pablo\runtimeError.py", line 1, in <module>
    numero = int(input("Ingrese un número entero: "))
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ValueError: invalid literal for int() with base 10: 'hola'

En este caso, el error se produce porque la función int() espera un valor que pueda convertir en número entero, pero recibe una cadena de caracteres en su lugar, generando un ValueError.

Si quisiéramos detectar los errores en tiempo de ejecución (no errores de sintaxis) Python cuenta con una estructura para estos casos.

try… except…

Imagina que estás aprendiendo a andar en bicicleta. La estructura try… except… es como cuando tus padres te están enseñando:

El bloque try

Este bloque es como cuando intentas montar la bicicleta: aquí pones el código que quieres probar, sabiendo que podría haber algún problema con este.

python
try:
    numero = int(input("Ingrese un número entero: "))
    print(f"El número ingresado es: {numero}")

En este caso, el código que queremos probar es la conversión de la entrada del usuario a un número entero. Si el usuario ingresa un valor que no es un número entero, el programa generaría un error de valor y se detendría. A menos que dicho código forme parte de un bloque try:. En este caso, try: funcionaría como un disparador (trigger en inglés) que enviaría el flujo del programa al bloque except: asociado. Esto evita que el programa se detenga abruptamente y, por el contrario, continúe ejecutándose.

El bloque except

Este bloque es como tener a tus padres cerca, listos para atraparte si te caes: aquí le dices a Python qué hacer si algo sale mal en el bloque "try", como debe comportarse en los casos excepcionales donde algo falla.

python

except ValueError:
    print("¡Ups! El valor ingresado no es un número entero.")

En este caso, cuando el usuario ingrese un valor que no sea un número entero, el programa no se detendrá abruptamente mostrando un error de valor; sino que, en su lugar, se ejecutará el bloque except mostrando el mensaje que le hemos indicado "¡Ups! El valor ingresado no es un número entero." en su lugar.

Así será la salida en pantalla al ejecutar nuestro programa:

Terminal (Entrada/Salida)
Ingrese un número entero: hola
¡Ups! El valor ingresado no es un número entero.

Si te ha quedado claro nuestro ejemplo, estudiemos la estructura try… except… en detalle.

Sintaxis

La estructura try… except… se escribe de la siguiente manera:

Python
try:
    # Código que quieres probar
    
except _tipo_de_error_:  # En nuestro ejemplo, ValueError
    # Código que se ejecutará si hay un error del tipo que se indique, en este caso, ValueError
    

La estructura posee dos palabras claves: try y except. Y funciona parecido a un if y un else. Pero cuidado, no son lo mismo. Podemos pensar su funcionamiento como que siempre se va a ejecutar el bloque try, y si algo sale mal en algún momento de la ejecución, el bloque try finaliza automáticamente su ejecución y pasa a ejecutarse bloque except correspondiente al tipo de error detectado.

Observemos otro detalle de como debemos escribir la estructura en nuestro programa:

python
# Solicitar al usuario un número entero y convertirlo a un número entero
try:
    # Solicitar al usuario un número entero
    # input siempre devuelve una cadena de caracteres.
    # Por lo tanto es necesario convertir la cadena a un número entero con la función int()
    numero = int(input("Ingrese un número entero: "))

    # Imprimir el número ingresado utilizando f-strings
    print(f"El número ingresado es: {numero}")

except ValueError:
    # Si el usuario ingresa un valor que no es un número entero, se ejecutará este bloque

    # Muestra un mensaje correspondiente al error de valor    
    print("¡Ups! El valor ingresado no es un número entero.")

La indentación1 es importante. Debemos respetar ambos bloques de código. El bloque try: conformado por las líneas de código 3 a 10 se encuentra indentado; al igual que el bloque except: conformado por las líneas de código 12 a 15 (aunque en ambos bloques existen muchas líneas de código que no son instrucciones si no que son comentarios que el intérprete ignorará).

Otros valores de error

Además de ValueError existen otros errores en tiempo de ejecución. A medida que profundicemos nuestros estudios veremos varios de ellos.

Y aunque en esencia representan diferentes problemas, la técnica para manejarlos será prácticamente la misma que acabamos de aprender.

¡Para recordar!

La estructura try… except… es una herramienta muy útil para manejar errores en tiempo de ejecución. Nos permite probar un bloque de código y, si algo sale mal, ejecutar un bloque de código alternativo. De esta manera, podemos evitar que nuestro programa se detenga abruptamente y, en su lugar, manejar los errores de una manera más elegante y controlada.

¿except… para todo tipo de error?

No se si te lo habrás preguntado a esta altura pero, ¿es necesario conocer el tipo de error de antemano para poder ejecutar un bloque except? ¿Qué sucedería en realidad si uno no puede anticiparse al momento de ejecución donde potencialmente podría ocurrir un tipo de error en particular?

Sucede que en algunos casos, como nuestro ejemplo de conversión de un valor ingresado por el usuario a un número entero, nosotros podemos de antemano suponer que podría dar un error de valor.

Pero en otros casos, esta detección no es tan sencilla. Aquí es donde entra en juego la palabra clave except sin especificar el tipo de error.

De esta manera, Python capturará cualquier tipo de error que ocurra en el bloque try y ejecutará el bloque except de manera generalizada.

Mala práctica

Aunque implementar un bloque except generalizado es una técnica útil an algunos casos, debes tener cuidado al hacerlo.

El problema con esto es que algunas veces podría ocultar otros errores en tu código porque no podrías saber necesariamente qué es lo que está fallando. Y si no puedes saber qué es lo que está fallando, ¿cómo podrías manejar el error correctamente?

Por lo tanto, sólo deberías implementar un bloque except cuando no queda otra salida. Hacerlo, por ejemplo, por el simple hecho de trabajar menos y meter todo error en la misma bolsa solo terminará por hacer tu código frágil, débil y poco confiable.

¡Buena práctica!

Siempre que puedas, intenta especificar el tipo de error que esperas en el bloque except. Debieras hacerlo de esta manera, aunque no siempre será obvio qué es lo que hay que comprobar. Sin embargo, con práctica adquirirás las habilidades correspondientes y mejorarás en esta tarea.

Piensa, deduce, prueba y averigua qué tipo de errores podrían ocurrir e inclúyelos explícitamente en un bloque except.

Recuerda que los distintos tipos de error se encuentran en la documentación oficial de Python, que puedes consultar haciendo clic aquí.

De esta manera, podrás manejar los errores de manera más precisa y efectiva.

Optimizar el bloque try

Volvamos a analizar nuestro programa:

python
# Solicitar al usuario un número entero y convertirlo a un número entero
try:
    # Solicitar al usuario un número entero
    # input siempre devuelve una cadena de caracteres.
    # Por lo tanto es necesario convertir la cadena a un número entero con la función int()
    numero = int(input("Ingrese un número entero: "))

    # Imprimir el número ingresado utilizando f-strings
    print(f"El número ingresado es: {numero}")

except ValueError:
    # Si el usuario ingresa un valor que no es un número entero, se ejecutará este bloque

    # Muestra un mensaje correspondiente al error de valor    
    print("¡Ups! El valor ingresado no es un número entero.")

Luego de estudiar y haber entendido la estructura try… except…, podemos ahora darnos cuenta que nuestro código, aunque es mucho más robusto y eficiente, todavía no está del todo bien en términos de lógica de programación. Esta no es la mejor manera de implementar este código.

¿Por qué?

Porque nuestra estructura de control try… except… solo debería contener la(s) línea(s) de código que podría(n) generar una excepción. En nuestro programa, la conversión de la entrada del usuario a un número entero, solamente.

¿Para qué incluir print(f"El número ingresado es: {numero}") dentro del bloque try: si no es necesario?

Esta instrucción no generará un error de valor, ya que la cadena de formato (f-string) de Python manejará la impresión sin problemas, independientemente del tipo de dato que sea numero.

Con esto en mente, nuestro código debería verse así:

python
# Solicitar al usuario un número entero y convertirlo a un número entero
try:
    # Solicitar al usuario un número entero
    # input siempre devuelve una cadena de caracteres.
    # Por lo tanto es necesario convertir la cadena a un número entero con la función int()
    numero = int(input("Ingrese un número entero: "))

except ValueError:
    # Si el usuario ingresa un valor que no es un número entero, se ejecutará este bloque

    # Muestra un mensaje correspondiente al error de valor    
    print("¡Ups! El valor ingresado no es un número entero.")


# Imprimir el número ingresado utilizando f-strings
print(f"El número ingresado es: {numero}")

Hemos sacado la línea print(f"El número ingresado es: {numero}") del bloque try: y la hemos colocado fuera de la estructura de control, ya que no es necesario que esté dentro de ella.

Pero, al hacer esto, hemos introducido un nuevo error en nuestro programa. ¿Puedes identificar cuál es? Piensa por un momento y luego avancemos al siguiente tema.

Referencias:


  1. Indentación: Si quieres repasar este concepto, puedes hacerlo en la unidad de funciones y subrutinas haciendo clic aquí