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!