Excepciones Personalizadas

En Python, las excepciones son una forma de responder a errores que pueden ocurrir durante la ejecución de un programa. Python tiene numerosas excepciones incorporadas como IndexError cuando intentas acceder a un índice que no existe en una lista, o TypeError cuando intentas realizar una operación en un tipo que no la soporta. Sin embargo, a veces puede ser útil definir nuestras propias excepciones. Esto nos permite dar mensajes de error significativos y responder a situaciones específicas que las excepciones incorporadas no pueden manejar. Las excepciones personalizadas se crean definiendo una nueva clase que hereda de la clase Exception incorporada o de una de sus subclases.
Consideremos un programa que trabaja con un sistema de biblioteca teórico. En este sistema, un usuario no debería poder sacar más de 5 libros a la vez. Aquí está cómo podríamos crear una excepción personalizada para esto:
class TooManyBooksError(Exception):  # Definir una nueva clase de excepción
    pass

def checkout_books(user, num_books):
    if num_books > 5:  
        # Lanzar una excepción si el usuario intenta sacar más de 5 libros
        raise TooManyBooksError('No puedes sacar más de 5 libros a la vez.')  # Lanzar nuestra excepción personalizada
    # De lo contrario, añadir los libros a la cuenta del usuario.
    user.books += num_books
En el ejemplo anterior, si un usuario intenta sacar más de 5 libros a la vez, se lanzará nuestra excepción personalizada TooManyBooksError con un mensaje de error específico. Si la excepción no es capturada, el programa detendrá la ejecución y el mensaje de error se imprimirá en la consola.
Podemos capturar esta excepción de la misma manera que las excepciones incorporadas usando un bloque try / except:
try:
    checkout_books(user, 7)     # Intentar sacar 7 libros.
except TooManyBooksError as e:  # Capturar nuestra excepción personalizada
    print(e)                    # Imprimir el mensaje de error.
Si la línea checkout_books(user, 7) lanza un TooManyBooksError, el programa lo capturará e imprimirá nuestro mensaje de error 'No puedes sacar más de 5 libros a la vez.'. De esta manera, podemos manejar el error de manera elegante y el programa puede continuar ejecutándose.
Al igual que con cualquier otra clase, podemos añadir métodos a nuestra clase de excepción. Por ejemplo, agreguemos un método que calcule cuántos libros el usuario necesita devolver para poder sacar la cantidad deseada:
class TooManyBooksError(Exception):  
    def __init__(self, attempted, limit=5, checked_out=0):  
        self.attempted = attempted
        self.limit = limit
        self.checked_out = checked_out
        self.message = f'Intentaste sacar {self.attempted} libros, pero el límite es {self.limit} libros.'
        super().__init__(self.message)
    
    def books_to_return(self):
        return self.checked_out + self.attempted - self.limit
Podemos usar este método después de capturar la excepción:
try:
    checkout_books(user, 7)
except TooManyBooksError as e:
    print(e)                                                   # Intentaste sacar 7 libros, pero el límite es 5 libros.
    print(f'Necesitas devolver {e.books_to_return()} libros.')  # Necesitas devolver 2 libros.
Así, las excepciones personalizadas pueden tener sus propios atributos y métodos, convirtiéndose en una herramienta poderosa para manejar errores de una manera adaptada a las necesidades específicas de tu programa.
 

Mejores Prácticas al Definir Excepciones Personalizadas

Al crear excepciones personalizadas, a menudo encontrarás tentador definir una clase vacía que hereda de Exception, y luego pasar todo el mensaje de error como una cadena cada vez que lanzas la excepción. Aunque este enfoque funciona, puede conducir a código repetitivo y hace más difícil mantener consistentes tus mensajes de excepción.
En su lugar, una mejor práctica es almacenar cualquier dato dinámico en la clase de excepción. De esta manera, puedes construir el mensaje de la excepción internamente, haciendo tu código más limpio y organizado:
👎 Mala práctica – pasar todo el mensaje cada vez:
class TooManyBooksError(Exception):
    pass

# Te ves obligado a pasar el mensaje cada vez
raise TooManyBooksError('Intentaste sacar 7 libros, pero el límite es 5!')

# Esto es una mala idea a largo plazo
👍 Una mejor opción – pasar los detalles dinámicos a la clase de excepción:
class TooManyBooksError(Exception):
    def __init__(self, attempted, limit=5):
        self.attempted = attempted
        self.limit = limit
        # Construir el mensaje de error a partir de los datos
        self.message = f'Intentaste sacar {self.attempted} libros, pero el límite es {self.limit}.'
        super().__init__(self.message)

# Al lanzar la excepción, solo pasa los datos relevantes
raise TooManyBooksError(7)
💡
Al permitir que la clase de excepción maneje la creación del mensaje de error, no te repites. Si necesitas cambiar la redacción o el formato del mensaje, solo tienes que hacerlo en un lugar: dentro del método __init__ de tu excepción.
 

Reto: Base de Datos de Edades

En una ciudad donde todos conocen a todos, el gobierno local está tratando de implementar un sistema de verificación de edad en los lugares de la ciudad como bares, clubes y teatros. Necesitan un programa que pueda, dado un diccionario de edades de personas, aceptar un nombre y devolver la edad de esa persona.
La entrada se maneja automáticamente. Debes implementar la función find_age(name) que, dado un nombre, devuelva la edad de la persona.
Sin embargo, no todas las personas pueden estar en la base de datos de la ciudad. Si el programa no puede encontrar un nombre en la base de datos, debe lanzar una excepción personalizada llamada NameNotFoundError y imprimir un mensaje de error apropiado: {name} not found in the database. Asegúrate de crear el mensaje dentro de la clase NameNotFoundError.
Entrada
Salida
5 Alice - 24 Bob - 32 Charlie - 21 Daisy - 27 Edgar - 30 Edgar
Edgar - 30
5 Alice - 24 Bob - 32 Charlie - 21 Daisy - 27 Edgar - 30 George
NameNotFoundError: George not found in the database.
Nota: Los nombres y las edades distinguen entre mayúsculas y minúsculas y pueden contener espacios. El nombre ingresado debe coincidir exactamente con el nombre en la base de datos.
 

Constraints

Time limit: 2 seconds

Memory limit: 512 MB

Output limit: 1 MB

To check your solution you need to sign in
Sign in to continue