Exceções Personalizadas

Em Python, as exceções são uma forma de responder a erros que podem ocorrer durante a execução de um programa. O Python tem numerosas exceções incorporadas, como IndexError quando tentamos aceder a um índice que não existe numa lista, ou TypeError quando tentamos realizar uma operação num tipo que não a suporta. No entanto, por vezes pode ser útil definir as nossas próprias exceções. Isto permite-nos fornecer mensagens de erro significativas e responder a situações específicas que as exceções incorporadas não conseguem lidar. As exceções personalizadas são criadas definindo uma nova classe que herda da classe Exception ou de uma das suas subclasses.
Vamos considerar um programa que trabalha com um sistema teórico de biblioteca. Neste sistema, um utilizador não deve poder requisitar mais de 5 livros ao mesmo tempo. Aqui está como podemos criar uma exceção personalizada para isto:
class TooManyBooksError(Exception):  # Definir uma nova classe de exceção
    pass

def checkout_books(user, num_books):
    if num_books > 5:  
        # Levantar uma exceção se o utilizador tentar requisitar mais de 5 livros
        raise TooManyBooksError('You cannot check out more than 5 books at a time.')  # Levantar a nossa exceção personalizada
    # Caso contrário, adicionar os livros à conta do utilizador.
    user.books += num_books
No exemplo acima, se um utilizador tentar requisitar mais de 5 livros ao mesmo tempo, a nossa exceção personalizada TooManyBooksError será levantada com uma mensagem de erro específica. Se a exceção não for capturada, o programa irá parar a execução, e a mensagem de erro será impressa no terminal.
Podemos capturar esta exceção da mesma forma que as exceções incorporadas, usando um bloco try / except:
try:
    checkout_books(user, 7)     # Tentar requisitar 7 livros.
except TooManyBooksError as e:  # Capturar a nossa exceção personalizada
    print(e)                    # Imprimir a mensagem de erro.
Se a linha checkout_books(user, 7) levantar uma TooManyBooksError, o programa irá capturá-la e imprimir a nossa mensagem de erro 'You cannot check out more than 5 books at a time.'. Desta forma, podemos lidar com o erro graciosamente e o programa pode continuar a correr.
Assim como com qualquer outra classe, podemos adicionar métodos à nossa classe de exceção. Por exemplo, vamos adicionar um método que calcula quantos livros o utilizador precisa devolver para requisitar a quantidade desejada:
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'You tried to check out {self.attempted} books, but the limit is {self.limit} books.'
        super().__init__(self.message)
        
    def books_to_return(self):
        return self.checked_out + self.attempted - self.limit
Podemos usar este método após capturarmos a exceção:
try:
    checkout_books(user, 7)
except TooManyBooksError as e:
    print(e)                                                   # Você tentou requisitar 7 livros, mas o limite é 5 livros.
    print(f'You need to return {e.books_to_return()} books.')  # Você precisa devolver 2 livros.
Assim, as exceções personalizadas podem ter os seus próprios atributos e métodos, tornando-as uma ferramenta poderosa para gerir erros de uma forma adaptada às necessidades específicas do seu programa.
 

Melhores Práticas ao Definir Exceções Personalizadas

Ao criar exceções personalizadas, muitas vezes pode ser tentador definir uma classe vazia que herda de Exception, e depois passar a mensagem de erro completa como uma string sempre que levanta a exceção. Embora esta abordagem funcione, pode levar a código repetitivo e torna mais difícil manter as suas mensagens de exceção consistentes.
Em vez disso, uma melhor prática é armazenar quaisquer dados dinâmicos na classe de exceção. Desta forma, pode construir a mensagem da exceção internamente, tornando o seu código mais limpo e organizado:
👎 Má prática – passar a mensagem inteira de cada vez:
class TooManyBooksError(Exception):
    pass

# É obrigado a passar a mensagem sempre
raise TooManyBooksError('You tried to check out 7 books, but the limit is 5!')

# Isto é uma má ideia a longo prazo
👍 Uma abordagem melhor – passando os detalhes dinâmicos para dentro da classe de exceção:
class TooManyBooksError(Exception):
    def __init__(self, attempted, limit=5):
        self.attempted = attempted
        self.limit = limit
        # Construir a mensagem de erro a partir dos dados
        self.message = f'You tried to check out {self.attempted} books, but the limit is {self.limit}.'
        super().__init__(self.message)

# Ao levantar a exceção, basta passar os dados relevantes
raise TooManyBooksError(7)
💡
Ao permitir que a classe da exceção trate da criação da mensagem de erro, evita repetir-se. Se precisar de alterar o texto ou a formatação da mensagem, só tem de o fazer num único lugar—dentro do método __init__ da sua exceção.
 

Desafio: Base de Dados de Idades

Numa cidade onde todos se conhecem, o governo local está a tentar implementar um sistema de verificação de idade em locais da cidade como bares, clubes e teatros. Eles precisam de um programa que, dado um dicionário de idades das pessoas, aceite um nome e devolva a idade dessa pessoa.
A entrada é tratada automaticamente. Deve implementar a função find_age(name) que, dado um nome, retorna a idade da pessoa.
No entanto, nem todas as pessoas podem estar na base de dados da cidade. Se o programa não conseguir encontrar um nome na base de dados, deve levantar uma exceção personalizada chamada NameNotFoundError e imprimir uma mensagem de erro apropriada: {name} not found in the database. Certifique-se de criar a mensagem dentro da classe NameNotFoundError.
Entrada
Saída
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: Os nomes e idades diferenciam maiúsculas de minúsculas e podem conter espaços. O nome introduzido deve corresponder exatamente ao nome na base de dados.
 

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