Como definir suas próprias classes de exceção em Python

Olá, Habr!



Seu interesse no novo livro " Python Pro Secrets " nos convenceu de que vale a pena continuar a história sobre as peculiaridades do Python. Hoje oferecemos a leitura de um pequeno tutorial sobre a criação de classes de exceção customizadas (no texto - suas próprias). O autor achou interessante, é difícil discordar dele que a vantagem mais importante de uma exceção é a completude e clareza da mensagem de erro. Parte do código do original está na forma de imagens.



Bem-vindo ao gato.



Criar suas próprias classes de erro



Python oferece a capacidade de criar suas próprias classes de exceção. Ao criar essas classes, você pode diversificar o design das classes em seu aplicativo. Uma classe personalizada de erros pode registrar erros e inspecionar o objeto. Somos nós que definimos o que a classe de exceção faz, embora normalmente uma classe personalizada dificilmente possa fazer mais do que exibir uma mensagem.



Naturalmente, o tipo de erro em si é importante e frequentemente criamos nossos próprios tipos de erro para indicar uma situação específica que geralmente não é abordada no nível da linguagem Python. Dessa forma, os usuários da classe saberão exatamente o que está acontecendo quando encontrarem esse erro.



Este artigo está dividido em duas partes. Primeiro, vamos definir uma classe de exceção por si só. Em seguida, demonstramos como podemos integrar nossas próprias classes de exceção em nossos programas Python e mostramos como melhorar a usabilidade das classes que projetamos dessa maneira.



Classe de exceção personalizada MyCustomError



Lançar uma exceção requer os métodos __init__()



e __str__()



.




Ao lançar uma exceção, já criamos uma instância da exceção e ao mesmo tempo a exibimos na tela. Vamos dar uma olhada em nossa classe de exceção personalizada mostrada abaixo.







A classe MyCustomError acima tem dois métodos mágicos __init__



e __str__



, que são chamados automaticamente durante o tratamento de exceções. O método Init



é chamado quando a instância é criada e o método str



é chamado quando a instância é exibida. Portanto, quando uma exceção é lançada, esses dois métodos geralmente são chamados imediatamente após o outro. A instrução de lançamento de exceção do Python coloca o programa em um estado de erro.



Na lista de argumentos do método __init__



é *args



. Um componente *args



é um modo especial de correspondência de padrões usado em funções e métodos. Ele permite que você passe vários argumentos e armazena os argumentos passados ​​como uma tupla, mas ao mesmo tempo permite que você não passe nenhum argumento.



No nosso caso, podemos dizer que se MyCustomError



algum argumento for passado para o construtor , então pegamos o primeiro argumento passado e o atribuímos a um atributo message



no objeto. Se nenhum argumento for passado, o atributo message



receberá um valor None



.



No primeiro exemplo, a exceção MyCustomError



é lançada sem nenhum argumento, então o atributo message



este objeto recebe um valor None



. Um método será chamado str



e exibirá a mensagem 'A mensagem MyCustomError foi gerada'.







A exceção MyCustomError



é lançada sem nenhum argumento (os parênteses estão vazios). Em outras palavras, esse projeto de objeto parece fora do padrão. Mas isso é apenas suporte sintático fornecido pelo Python ao lançar uma exceção.



No segundo exemplo, MyCustomError



é passado com um argumento string 'Temos um problema'. Ele é definido como um atributo do message



objeto e exibido como uma mensagem de erro quando uma exceção é lançada.







O código para a classe de exceção MyCustomError está aqui...



class MyCustomError(Exception):
    def __init__(self, *args):
        if args:
            self.message = args[0]
        else:
            self.message = None

    def __str__(self):
        print('calling str')
        if self.message:
            return 'MyCustomError, {0} '.format(self.message)
        else:
            return 'MyCustomError has been raised'


#  MyCustomError

raise MyCustomError('We have a problem')
      
      





Classe CustomIntFloatDic



Criamos nosso próprio dicionário, cujos valores só podem ser inteiros e números de ponto flutuante.



Vamos prosseguir e demonstrar como injetar classes de erro de maneira fácil e útil em nossos próprios programas. Para começar, vou dar um exemplo ligeiramente artificial. Neste exemplo fictício, vou criar meu próprio dicionário que só pode aceitar números inteiros ou números de ponto flutuante.



Se o usuário tentar especificar qualquer outro tipo de dados como o valor neste dicionário, uma exceção será lançada. Esta exceção fornecerá informações úteis ao usuário sobre como usar este dicionário. No nosso caso, esta mensagem informa diretamente ao usuário que apenas números inteiros ou números de ponto flutuante podem ser especificados como valores neste dicionário.



Ao criar seu próprio dicionário, lembre-se de que existem dois lugares onde os valores podem ser adicionados ao dicionário. Em primeiro lugar, isso pode acontecer no método init ao criar um objeto (neste estágio, o objeto já pode ter chaves e valores atribuídos) e, em segundo lugar, ao definir chaves e valores diretamente no dicionário. Em ambos os lugares, você precisa escrever código para garantir que o valor só possa ser do tipo int



ou float



.



Primeiro, definirei a classe CustomIntFloatDict que herda da classe interna dict



. dict



é passado em uma lista de argumentos, que são colocados entre parênteses e seguem o nome da classe CustomIntFloatDict



.



Se uma classe é instanciada CustomIntFloatDict



, além disso, nenhum argumento é passado para os parâmetros de chave e valor, eles serão definidos como None



. A expressão é if



interpretada da seguinte maneira: se a chave for igual None



ou o valor for igual None



, um método será chamado com o objeto get_dict()



, que retornará o atributo empty_dict



; tal atributo em um objeto aponta para uma lista vazia. Lembre-se de que os atributos de classe estão disponíveis em todas as instâncias da classe.







O objetivo desta classe é permitir que o usuário passe uma lista ou tupla com as chaves e valores dentro dela. Se o usuário inserir uma lista ou tupla procurando chaves e valores, esses dois conjuntos enumerados serão concatenados usando a função zip



a linguagem Python. Uma variável conectada apontando para um objeto zip



é iterável e as tuplas são desempacotáveis. Ao iterar as tuplas, verifico se val é uma instância da classe int



ou float



. Se val



não pertencer a nenhuma dessas classes, lanço IntFloatValueError



minha própria exceção e passo val como um argumento para ela.



Classe de exceção IntFloatValueError



Quando uma exceção é lançada, IntFloatValueError



criamos uma instância da classe IntFloatValueError



e a exibimos simultaneamente na tela. Isso significa que os métodos mágicos init



e serão chamados str



.



O valor que causou a exceção lançada é definido como um atributo que value



acompanha a classe IntFloatValueError



. Ao chamar o método magic str, o usuário recebe uma mensagem de erro informando que o valor init



em CustomIntFloatDict



é inválido. O usuário sabe o que fazer para corrigir esse erro.







Classes de exceção IntFloatValueError



e KeyValueConstructError











se nenhuma exceção for lançada, isto é, todos val



do objeto encadeado são de tipos int



ou float



, então eles serão configurados usando __setitem__()



, e o método da classe pai fará tudo para nós dict



, conforme mostrado abaixo.







Classe KeyValueConstructError



O que acontece se o usuário inserir um tipo que não seja uma lista ou uma tupla de chaves e valores?



Novamente, este exemplo é um pouco artificial, mas é útil para mostrar como você pode usar suas próprias classes de exceção.



Se o usuário não especificar as chaves e valores como uma lista ou tupla, uma exceção será lançada KeyValueConstructError



. O objetivo desta exceção é informar ao usuário que, para escrever chaves e valores em um objeto CustomIntFloatDict



, uma lista ou tupla deve ser especificada no construtor da init



classe CustomIntFloatDict



.



No exemplo acima, como o segundo argumento para o construtor init



muito foi passado e uma exceção foi lançada por causa disso KeyValueConstructError



. O benefício da mensagem de erro exibida é que a mensagem de erro exibida informa ao usuário que as chaves e os valores a serem inseridos devem ser relatados como uma lista ou uma tupla.



Novamente, quando uma exceção é lançada, uma instância KeyValueConstructError é criada e a chave e os valores são passados ​​como argumentos para o construtor KeyValueConstructError. Eles são definidos como os valores dos atributos de chave e valor de KeyValueConstructError e são usados ​​no método __str__ para gerar uma mensagem de erro significativa quando a mensagem é exibida na tela.



Além disso, eu até incluo os tipos de dados inerentes aos objetos adicionados ao construtor init



- Eu faço isso para maior clareza.







Definir chave e valor em CustomIntFloatDict



CustomIntFloatDict



herda de dict



. Isso significa que funcionará exatamente como um dicionário, exceto nos locais que escolhermos alterar seletivamente seu comportamento.



__setitem__



É um método mágico chamado ao definir uma chave e um valor em um dicionário. Em nossa implementação, setitem



verificamos se o valor é do tipo int



ou float



, e somente após uma verificação bem-sucedida ele pode ser definido no dicionário. Se a verificação falhar, você pode usar a classe de exceção novamente IntFloatValueError



. Aqui você pode ter certeza de que obteremos uma exceção ao tentar definir uma string ‘bad_value’



como um valor no dicionário test_4



.







Todo o código para este tutorial é mostrado abaixo e postado no Github .



#  ,        int  float  

class IntFloatValueError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return '{} is invalid input, CustomIntFloatDict can only accept ' \
               'integers and floats as its values'.format(self.value)


class KeyValueContructError(Exception):
    def __init__(self, key, value):
        self.key = key
        self.value = value

    def __str__(self):
        return 'keys and values need to be passed as either list or tuple' + '\n' + \
                ' {} is of type: '.format(self.key) + str(type(self.key)) + '\n' + \
                ' {} is of type: '.format(self.value) + str(type(self.value))


class CustomIntFloatDict(dict):

    empty_dict = {}

    def __init__(self, key=None, value=None):

        if key is None or value is None:
            self.get_dict()

        elif not isinstance(key, (tuple, list,)) or not isinstance(value, (tuple, list)):
            raise KeyValueContructError(key, value)
        else:
            zipped = zip(key, value)
            for k, val in zipped:
                if not isinstance(val, (int, float)):
                    raise IntFloatValueError(val)
                dict.__setitem__(self, k, val)

    def get_dict(self):
        return self.empty_dict

    def __setitem__(self, key, value):
        if not isinstance(value, (int, float)):
            raise IntFloatValueError(value)
        return dict.__setitem__(self, key, value)


#  

# test_1 = CustomIntFloatDict()
# print(test_1)
# test_2 = CustomIntFloatDict({'a', 'b'}, [1, 2])
# print(test_2)
# test_3 = CustomIntFloatDict(('x', 'y', 'z'), (10, 'twenty', 30))
# print(test_3)
# test_4 = CustomIntFloatDict(('x', 'y', 'z'), (10, 20, 30))
# print(test_4)
# test_4['r'] = 1.3
# print(test_4)
# test_4['key'] = 'bad_value'
      
      





Conclusão



Se você criar suas próprias exceções, trabalhar com a classe se tornará muito mais conveniente. A classe de exceção deve ter métodos mágicos init



e str



que são chamados automaticamente durante o tratamento de exceções. Depende inteiramente de você o que sua classe de exceção fará. Entre os métodos apresentados estão os responsáveis ​​por inspecionar um objeto e exibir uma mensagem informativa de erro na tela.



Seja como for, as classes de exceção tornam muito mais fácil lidar com quaisquer erros que surjam!



All Articles