Saltar al contenido principal

Propagación

La propagación de excepciones en Python se refiere al comportamiento de las excepciones cuando estas no son capturadas en un bloque de código y "se propagan" hacia otros bloques, hasta que encuentran un try-except que las capture, o hasta que llegan al nivel más alto del programa, lo que genera un mensaje de error y finaliza la ejecución del programa.

  1. Generación de una excepción:

    Una excepción ocurre cuando se detecta un error durante la ejecución del código. Por ejemplo, dividir por cero o intentar acceder a un índice fuera de rango genera una excepción.

    x = 1 / 0  # Esto genera una excepción
  2. Búsqueda de un bloque try-except:

    Cuando ocurre una excepción, Python busca un bloque try-except en el código actual que pueda manejar ese tipo de excepción. Si encuentra uno, la ejecución salta al bloque except correspondiente y el resto del bloque try se omite.

    try:
    x = 1 / 0
    except:
    print("¡No se puede dividir por cero!")

    En este caso, la excepción es capturada y manejada correctamente, imprimiendo el mensaje "¡No se puede dividir por cero!".

  3. Propagación de la excepción:

    Si la excepción no se captura en el bloque try-except actual, la excepción se propaga hacia afuera, buscando en el siguiente nivel de la pila de llamadas (otro bloque try-except en funciones que llamaron a la función actual) hasta que encuentra un manejador que pueda capturarla.

    def funcion_b():
    x = 1 / 0 # Excepción aquí

    def funcion_a():
    funcion_b() # Llama a funcion_b

    try:
    funcion_a() # Llama a funcion_a
    except:
    print("¡Capturada en la función principal!")

    Aquí, la excepción se produce en funcion_b y, como no hay un manejador para capturarla allí, la excepción se propaga hacia funcion_a y, finalmente, hasta el bloque try-except en la función principal, donde es capturada y manejada.

  4. Excepción no capturada:

    Si la excepción no se captura en ningún nivel de la pila de llamadas, llega al nivel más alto del programa. En este caso, Python mostrará un mensaje de error (el traceback) y finalizará la ejecución del programa.

    def funcion_b():
    x = 1 / 0

    def funcion_a():
    funcion_b()

    funcion_a() # Sin bloque try-except

    # Resultado:
    # ZeroDivisionError: division by zero

    En este caso, como no hay ningún bloque try-except, la excepción no se captura y el programa termina con un error.

Ejemplos

Tenemos el siguiente programa:

def funcion_b():
x = 1 / 0
return x

def funcion_a():
a = funcion_b()
return a

var = funcion_a()
print(var)

Como vimos, al no capturar la excepción, se provoca un error en la ejecución del programa. Por lo tanto debemos capturar la excepción. Tenemos varios lugares donde capturarla. Por ejemplo, capturémosla en el programa principal:

def funcion_b():
x = 1 / 0
return x

def funcion_a():
a = funcion_b()
return a

try:
var = funcion_a()
except:
var = 1
print(var)

En este caso, la instrucción x = 1 / 0 provoca una excepción. Al no capturarse, esta se propaga a la función que llamó a esta función, en este caso funcion_a() en la instrucción a = funcion_b(). Esta línea tampoco es capturada por una excepción, entonces se propaga a la función que llamó a esta, que en este caso es el programa principal. Aquí sí que se captura, y por lo tanto se ejecuta el bloque de código del except y el programa imprimirá el valor 1.

Pero podemos capturar la excepción en otro lugar, como puede ser en la funcion_a():

def funcion_b():
x = 1 / 0
return x

def funcion_a():
try:
a = funcion_b()
except:
a = 2
return a

var = funcion_a()
print(var)

En este caso, la instrucción x = 1 / 0 provoca una excepción. Al no capturarse, esta se propaga a la función que llamó a esta función, en este caso funcion_a() en la instrucción a = funcion_b(). En esta línea se captura la excepción, por lo que a la variable a se le asigna el valor 2. El programa continúa su ejecución normal y por lo tanto imprimirá el valor 2.

También es posible capturar la excepción en la funcion_b() donde se provoca:

def funcion_b():
try:
x = 1 / 0
except:
x = 3
return x

def funcion_a():
a = funcion_b()
return a

var = funcion_a()
print(var)

Del mismo modo que en los casos anteriores x = 1 / 0 provoca una excepción, pero en este caso ya se captura en la propia funcion_b(), por lo que esta función devuelve el valor 3. El programa continúa su funcionamiento normal, por lo que se imprimirá el valor 3.

Pero, ¿qué pasa si intentamos capturar una excepción varias veces? Pues no es posible, porque una excepción sólo se puede capturar una única vez.

Supongamos este ejemplo que es una mezcla de los anteriores:

def funcion_b():
try:
x = 1 / 0
except:
x = 3
return x

def funcion_a():
try:
a = funcion_b()
except:
a = 2
return a

try:
var = funcion_a()
except:
var = 1
print(var)

La excepción provocada por la instrucción x = 1 / 0 ya es capturada en la propia funcion_b, por lo que los bloques try de funcion_a y del programa principal no capturarán esta excepción. Por lo tanto, el programa imprimirá el valor 3.