Aqui está uma tarefa que é para ser simples mas que considero mal documentada no mundo Python.
O problema: estou desenvolvendo uma biblioteca para simular uma determinada topologia de ímãs permanentes. O usuário deve entrar com alguns parâmetros geométricos, mas antes de qualquer coisa o programa deve verificá-los e avisar quais são inválidos.
Em termos de programação, estamos falando de exceções: condições literalmente excepcionais que não devem acontecer mas, se acontecem, é melhor parar o programa para que o usuário possa verificar. O tutorial oficial de Python explica como criar exceções personalizadas, e a documentação de referência mostra os tipos de exceções que já existem em Python. Este tópico também é bem abordado em [1].
Pesquisando a documentação sobre as exceções já construídas, parece-me que a exceção aproximada que descreve esse problema é ValueError
. O que eu quero então é definir um erro que diga que o “valor errado” é precisamente um “parâmetro inválido”. O problema na documentação que citei antes é precisamente que eu não estava conseguindo entender como a mensagem de erro pode ser customizada para indicar qual parâmetro é inválido, pois as exceções aparentemente não aceitam strings como argumentos…
…mas exceções aceitam uma tupla como argumento, e é isso que não estava claro para mim. Veja o código a seguir:
class InvalidParameterError(ValueError): | |
pass |
from custom_errors import InvalidParameterError | |
def validate(p): | |
for k, v in p.items(): | |
if v < 0: | |
emsg = "Parameter '%s' cannot be negative" %(k,) | |
raise InvalidParameterError(emsg) |
import pytest | |
from custom_error import InvalidParameterError | |
from parse_parameters import validate | |
def test_negative_parameters_raise_error(): | |
sample_params = { | |
'a': 1.43, | |
'b': 57, | |
'c': -10 | |
} | |
with pytest.raises(InvalidParameterError) as excinfo: | |
validate(sample_params) | |
# The object returned by this pytest context manager | |
# has a 'value' field assigned to the exception object itself. | |
# In addition, all built-in exceptions derived from Exception | |
# have an 'args' tuple field assigned to the passed arguments. | |
# Hence, when we create a custom error with a string as the first argument, | |
# this string is assigned to the first element of the tuple 'args' | |
exception_msg = excinfo.value.args[0] | |
expected_message = "Parameter 'c' cannot be negative". | |
assert exception_msg == expected_message | |
No primeiro programa, eu defino uma exceção que deriva de ValueError
. No segundo, crio uma função de validação, que cria essa minha exceção personalizada, e a constrói com uma string de mensagem indicando qual foi o problema. E posso testar essa função usando pytest
, conforme está nos comentários do terceiro programa: tudo que eu passei para InvalidParameterError
foi armazenado internamente como uma tupla args
; se só passei uma string, esta foi armazenada no primeiro elemento, ou seja, em args[0]
. E posso testar se a mensagem criada era a que eu estava esperando.
A minha biblioteca de ímãs permanentes é mais complexa que isto, mas a lógica é idêntica: se alguém tentar usá-la com um parâmetro inválido, o programa vai fazer uma verificação e imprimir uma mensagem de InvalidParameterError
na tela.
É como falei no início: era para ser simples, mas no meio do caminho me diverti muito aprendendo sobre exceções em Python e escrevendo sobre elas. E agora vou voltar a realmente trabalhar na minha biblioteca, e estou muito mais seguro de que ela vai funcionar como deveria.
[1]: Beazley, David & Jones, Brian K. Python Cookbook. São Paulo: Novatec, 2013 (link afiliado para compra)