Tiza Paso a paso
Practicar →

Pensamiento Computacional · Unidad 5 — Entrada, salida y errores

Manejo de errores

try / except deja que el programa siga cuando algo falla. Pero atrapar todo sin mirar esconde los errores en vez de arreglarlos.

Practicar este tema →8 ejercicios que te corrigen paso a paso · gratis, sin cuenta

La explicación

El problema

Un error corta el programa. Si el usuario escribe "hola" donde iba un número, int() tira ValueError y todo lo que venía después no se ejecuta.

try y except

try:
    edad = int(input("Edad: "))
except ValueError:
    print("Eso no es un número")

Python intenta lo del try. Si sale bien, saltea el except. Si falla con ese error, ejecuta el except y el programa sigue.

Los errores que más vas a ver

ValueError         int("hola")         el tipo está bien, el valor no
TypeError          "a" + 1             tipos incompatibles
ZeroDivisionError  10 / 0
IndexError         [1,2][5]            posición que no existe
KeyError           d["pepe"]           clave que no existe
FileNotFoundError  open("no.txt")
NameError          usar una variable no definida
Leer el mensaje de error es media solución. Python te dice el tipo, la línea y muchas veces qué valor lo causó. Un TypeError que menciona NoneType casi siempre significa que una función terminó sin return — lo que vimos en el tema 2.5.

Varios except

try:
    resultado = int(a) / int(b)
except ValueError:
    print("No son números")
except ZeroDivisionError:
    print("No se puede dividir por cero")

Se ejecuta el primero que coincida, igual que un elif.

El except vacío, y por qué no conviene

try:
    ...
except:      # atrapa absolutamente todo
    pass
Esto esconde los errores en vez de manejarlos. Un error de tipeo en un nombre de variable daría NameError, quedaría atrapado ahí, y el programa seguiría como si nada — dando resultados mal sin que nadie se entere. Atrapá el error que esperás, no todos.

Los dos bloques que faltan: else y finally

try:
    r = a / b
except ZeroDivisionError:
    print("no se puede dividir por cero")
else:
    print("salió bien")   # sólo si el try NO falló
finally:
    print("esto corre siempre")

El else de un try es lo contrario del else de un if: corre cuando no hubo excepción. Sirve para dejar en el try sólo la línea riesgosa y todo lo demás afuera.

finally corre siempre, haya fallado o no, y aunque en el medio haya un return: Python se guarda el valor que ibas a devolver, ejecuta el finally y recién ahí sale de la función. Por eso es el lugar donde se cierra lo que hay que cerrar — un archivo, una conexión — cuando no estás usando with.

Validar en un ciclo

while True:
    try:
        edad = int(input("Edad: "))
        break            # sólo llega acá si el int() funcionó
    except ValueError:
        print("Poné un número entero")

Es el patrón estándar para pedir un dato hasta que sea válido. El break va después de la línea que puede fallar: si falla, nunca se alcanza.

Un ejercicio resuelto, paso a paso

Escribir una función que pida un número por teclado y no siga hasta que el usuario escriba uno válido.

def pedir_numero():
    while True:
        try:
            return int(input("Número: "))
        except ValueError:
            print("Eso no es un número entero. Probá de nuevo.")

Cómo corre. Si el usuario escribe 25, el int() funciona, el return devuelve 25 y la función termina. Si escribe hola, el int() falla, se ejecuta el except, y el while True da otra vuelta.

El return hace de break. Está en la misma línea que puede fallar: si int() explota, nunca se llega a devolver nada. Si hubieras escrito
numero = input("Número: ")
return int(numero)   ← el error ocurre acá
el resultado sería el mismo, pero es más fácil equivocarse y poner el return afuera del try.

Por qué except ValueError y no except a secas. Si el usuario corta el programa con Ctrl+C, un except vacío lo atraparía también y no habría forma de salir. Atrapando sólo ValueError, todo lo demás sigue funcionando normal.

Dónde entrarían else y finally. Acá no hacen falta, pero si quisieras avisar "listo" sólo cuando la conversión salió bien, ese aviso va en un else; y si tuvieras que cerrar algo pase lo que pase, va en un finally — que corre incluso con ese return adentro del try.

Control: probá con un número, con texto, y con el Enter vacío. Los tres tienen que comportarse como esperás — el vacío también es ValueError.

Y uno del nivel del parcial

Escribir una función dividir(a, b) que devuelva a/b y no se rompa nunca: si b es cero tiene que devolver un mensaje, y si alguno de los dos no es un número, otro. Explicar por qué conviene nombrar los errores en vez de usar un except suelto.

Los dos errores que puede tirar una división, y son distintos:

10 / 0     → ZeroDivisionError
"a" / 2    → TypeError

La función, con un except por cada uno:

def dividir(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "No se puede dividir por cero"
    except TypeError:
        return "Los dos tienen que ser numeros"

Los except se prueban en orden y se ejecuta el primero que coincida con el error que salió. Los demás se saltean.

Adentro del try va solo la línea que puede fallar. Si se mete código de más, un error en cualquier otra parte también quedaría atrapado y reportado como si fuera una división por cero.

Las pruebas:

dividir(10, 2)  → 5.0
dividir(-9, 3)  → -3.0
dividir(1, 0)   → "No se puede dividir por cero"
dividir("a", 2) → "Los dos tienen que ser numeros"
dividir(2, "a") → "Los dos tienen que ser numeros"

Ahora la segunda parte: por qué no usar un except suelto.

try:
    return a / b
except: # NO
    return "Hubo un error"

Eso atrapa absolutamente todo, incluidos los errores que no tienen nada que ver:

un nombre mal escrito (NameError)
un índice fuera de rango (IndexError)
el Ctrl+C con el que querés cortar el programa
cualquier error tuyo, adentro del try

El resultado es un programa que nunca se rompe y nunca anda. Si escribiste a / bb por error, con un except suelto el programa contesta "Hubo un error" tranquilamente, y podés pasar horas buscando un bug que el propio except está escondiendo.

La regla: atrapar solo los errores que sabés que pueden pasar y para los que tenés una respuesta. Si no sabés qué hacer con un error, es mejor que el programa se rompa y te muestre dónde.

Una variante que conviene conocer, cuando los dos errores merecen la misma respuesta:

except (ZeroDivisionError, TypeError):
    return "Datos invalidos"

Y otra, si lo que se quiere es avisar y seguir: se puede usar except ... as e para ver el mensaje original de Python:

except ZeroDivisionError as e:
    print("Error:", e) # Error: division by zero
    return None

Cuándo devolver un mensaje y cuándo devolver None. Devolver un texto es cómodo para mostrar, pero incómodo para seguir calculando: quien llama tiene que revisar si le devolvieron un número o una frase. Si el resultado se va a usar en más cuentas, conviene None y que el que llama decida.

Dónde se pierde el punto: poner el except TypeError antes del ZeroDivisionError creyendo que el orden no importa. Acá da igual porque los dos errores no están emparentados, pero con except Exception primero, todos los demás quedan muertos: los más específicos van siempre arriba.

Dónde se cae la mayoría

Hasta acá la explicación. La práctica es la otra mitad: 8 ejercicios de este tema, en cuatro niveles, que no te dicen sólo si está bien al final sino que te corrigen en cada paso y te explican por qué. Y uno para escribir el programa vos, que se prueba ejecutándolo en Python ahí mismo, en el navegador. Además trae una animación que muestra la idea en movimiento.

Practicar Manejo de errores →

Dónde entra este tema en el parcial

Los otros temas de la unidad 5

Entrada, salida y errores

Ver todos los temas de Pensamiento Computacional