Qual exceção devo criar em combinações de argumentos ruins / ilegais no Python?

544

Eu estava pensando sobre as melhores práticas para indicar combinações de argumentos inválidos em Python. Eu me deparei com algumas situações em que você tem uma função assim:

def import_to_orm(name, save=False, recurse=False):
    """
    :param name: Name of some external entity to import.
    :param save: Save the ORM object before returning.
    :param recurse: Attempt to import associated objects as well. Because you
        need the original object to have a key to relate to, save must be
        `True` for recurse to be `True`.
    :raise BadValueError: If `recurse and not save`.
    :return: The ORM object.
    """
    pass

O único incômodo com isso é que cada pacote tem o seu, geralmente um pouco diferente BadValueError. Eu sei que em Java existe java.lang.IllegalArgumentException- é bem entendido que todos estarão criando seus próprios BadValueErrors em Python ou existe outro método preferido?

cdleary
fonte

Respostas:

608

Eu apenas aumentaria ValueError , a menos que você precise de uma exceção mais específica.

def import_to_orm(name, save=False, recurse=False):
    if recurse and not save:
        raise ValueError("save must be True if recurse is True")

Não há realmente sentido em fazer class BadValueError(ValueError):pass- sua classe personalizada é idêntica em uso ao ValueError , então por que não usar isso?

dbr
fonte
65
> "então por que não usar isso?" - especificidade. Talvez eu queira pegar em uma camada externa "MyValueError", mas não em nenhum / todos os "ValueError".
Kevin Pouco
7
Sim, então parte da questão da especificidade é onde mais o ValueError é gerado. Se a função de chamada gosta de seus argumentos, mas chama internamente math.sqrt (-1), um chamador pode estar pegando ValueError esperando que seus argumentos sejam inapropriados. Talvez você só verificar a mensagem, neste caso ...
cdleary
3
Não tenho certeza de que esse argumento seja válido: se alguém está ligando math.sqrt(-1), é um erro de programação que precisa ser corrigido de qualquer maneira. ValueErrornão se destina a ser capturado na execução normal do programa ou do qual derivaria RuntimeError.
ereOn
2
Se o erro estiver no NÚMERO de argumentos, para uma função com um número variável de argumentos ... por exemplo, uma função em que os argumentos devem ser um número par de argumentos, você deve gerar um TypeError para ser consistente. E não faça sua própria classe, a menos que a) você tenha um caso de uso ou b) esteja exportando a biblioteca para ser usada por outras pessoas. Funcionalidade prematura é a morte do código.
Erik Aronesty
104

Eu herdaria de ValueError

class IllegalArgumentError(ValueError):
    pass

Às vezes, é melhor criar suas próprias exceções, mas herdar de uma embutida, o mais próximo possível do que você deseja.

Se você precisar capturar esse erro específico, é útil ter um nome.

Markus Jarderot
fonte
26
Parar aulas de redação e exceções personalizadas - pyvideo.org/video/880/stop-writing-classes
Hamish Grubijan
40
@HamishGrubijan esse vídeo é terrível. Quando alguém sugeria um bom uso de uma aula, ele apenas dizia "Não use aulas". Brilhante. As aulas são boas. Mas não aceite minha palavra .
Rob Grant
12
@RobertGrant Não, você não entendeu. Esse vídeo não é sobre literalmente "não use classes". Trata-se de não complicar demais as coisas.
RayLuo 31/08/16
15
@RayLuo, você pode ter verificado a sanidade do que o vídeo está dizendo e o convertido em uma mensagem alternativa palatável e sensata, mas é isso que o vídeo diz, e é o que alguém que não tem muita experiência e bom senso vai sair com.
Rob Grant
3
@SamuelSantana como eu disse, sempre que alguém levanta a mão e diz "e X?" onde X era uma boa ideia, ele apenas disse: "não faça outra aula". Bem claro. Eu concordo que a chave é o equilíbrio; o problema é que é apenas demasiado vago para realmente ao vivo por :-)
Rob Grant
18

Eu acho que a melhor maneira de lidar com isso é a maneira como o próprio python lida com isso. Python gera um TypeError. Por exemplo:

$ python -c 'print(sum())'
Traceback (most recent call last):
File "<string>", line 1, in <module>
TypeError: sum expected at least 1 arguments, got 0

Nosso desenvolvedor júnior acabou de encontrar esta página em uma pesquisa no google por "argumentos errados de exceção de python" e estou surpreso que a resposta óbvia (para mim) nunca tenha sido sugerida na década desde que essa pergunta foi feita.

J Bones
fonte
8
Nada me surpreende, mas concordo 100% que TypeError é a exceção correta se o tipo estiver errado em alguns dos argumentos passados ​​para a função. Um ValueError seria adequado se as variáveis ​​forem do tipo correto, mas seu conteúdo e valores não fizerem sentido.
user3504575
Eu acho que isso é provavelmente por falta ou não de argumentos, enquanto a pergunta é sobre argumentos que são fornecidos corretamente, mas estão incorretos em um nível de abstração mais alto, envolvendo o valor do argumento fornecido. Mas, como eu estava realmente procurando pelo primeiro, faça um voto positivo de qualquer maneira.
26519 ninguem
2
Como @ user3504575 e @Nobody disseram, TypeError é usado se os argumentos não corresponderem à assinatura da função (número incorreto de argumentos posicionais, argumentos de palavras-chave com o nome errado, tipo de argumento errado), mas um ValueError é usado quando a chamada de função corresponde à assinatura, mas os valores do argumento são inválidos (por exemplo, chamando int('a')). fonte
goodmami 24/10/19
Como a pergunta do OP se refere a "combinações inválidas de argumentos", parece que um TypeError seria apropriado, pois seria o caso em que a assinatura da função está essencialmente errada para os argumentos passados.
JBones
Seu exemplo chama sum()sem argumentos, que é um TypeError, mas o OP estava preocupado com combinações "ilegais" de valores de argumento quando os tipos de argumento estão corretos. Nesse caso, ambos savee recursesão bools, mas se recursefor, Trueentão savenão deve ser False. Isto é um ValueError. Concordo que alguma interpretação do título da pergunta seria respondida por TypeError, mas não para o exemplo apresentado.
Goodmami 29/10/19
11

Acabei de ver o componente ValueErrorusado nessa situação.

Eli Courtwright
fonte
8

Depende de qual é o problema com os argumentos.

Se o argumento tiver o tipo errado, aumente um TypeError. Por exemplo, quando você obtém uma string em vez de um desses booleanos.

if not isinstance(save, bool):
    raise TypeError(f"Argument save must be of type bool, not {type(save)}")

Note, no entanto, que no Python raramente fazemos verificações como essa. Se o argumento for realmente inválido, alguma função mais profunda provavelmente fará a reclamação por nós. E se verificarmos apenas o valor booleano, talvez algum usuário de código mais tarde a alimente com uma string sabendo que as strings não vazias são sempre True. Isso pode lhe salvar um elenco.

Se os argumentos tiverem valores inválidos, aumente ValueError. Isso parece mais apropriado no seu caso:

if recurse and not save:
    raise ValueError("If recurse is True, save should be True too")

Ou, nesse caso específico, ter um valor True de recurse implica um valor True de save. Como eu consideraria isso uma recuperação de um erro, você também pode reclamar no log.

if recurse and not save:
    logging.warning("Bad arguments in import_to_orm() - if recurse is True, so should save be")
    save = True
Gloweye
fonte
Eu acho que essa é a resposta mais precisa. Isto é obviamente subestimado (7 votos até agora, incluindo o meu).
Siu Ching Pong -Asuka Kenji-
-1

Eu não sei se concordo com a herança de ValueError- minha interpretação da documentação é que ValueErroré única deveria ser levantada por builtins ... herdar a partir dele ou elevando-se parece incorreta.

Gerado quando uma operação ou função interna recebe um argumento que tem o tipo certo, mas um valor inadequado, e a situação não é descrita por uma exceção mais precisa, como IndexError.

- documentação ValueError

cdleary
fonte
Compare google.com/codesearch?q=lang:python+class \ + \ w Erro (([^ E] \ w * | E [^ x] \ w )): com google.com/codesearch?q=lang: python + class \ + \ w * Erro (Exceção):
Markus Jarderot 1/11/2008
13
Essa sinopse significa simplesmente que os embutidos aumentam e não apenas os embutidos podem aumentá-lo. Nesse caso, não seria totalmente apropriado que a documentação do Python falasse sobre o que as bibliotecas externas geram.
Ignacio Vazquez-Abrams
5
Cada parte do software Python que eu já vi foi usada ValueErrorpara esse tipo de coisa, então acho que você está tentando ler muito na documentação.
James Bennett
6
Err, se usarmos as pesquisas do Código do Google para argumentar sobre isso: google.com/codesearch?q=lang%3Apython+raise%5C+ValueError # 66.300 casos de aumento do ValueError, incluindo Zope, xen, Django, Mozilla (e isso é apenas da primeira página de resultados). Se uma exceção interna se encaixar, use-a.
DBR
7
Como afirmado, a documentação é ambígua. Deveria ter sido escrito como "Gerado quando uma operação interna ou uma função interna recebe" ou como "Gerado quando uma função ou operação interna recebe". Obviamente, qualquer que seja a intenção original, a prática atual a superou (como @dbr aponta). Portanto, ele deve ser reescrito como a segunda variante.
Eponymous
-1

Concorde com a sugestão de Markus de lançar sua própria exceção, mas o texto da exceção deve esclarecer que o problema está na lista de argumentos, não nos valores individuais dos argumentos. Eu proporia:

class BadCallError(ValueError):
    pass

Usado quando faltam argumentos de palavra-chave necessários para a chamada específica ou valores de argumento individualmente válidos, mas inconsistentes entre si. ValueErrorainda estaria certo quando um argumento específico é do tipo certo, mas está fora do intervalo.

Isso não deveria ser uma exceção padrão no Python?

Em geral, eu gostaria que o estilo Python fosse um pouco mais preciso ao distinguir entradas ruins de uma função (falha do chamador) e resultados ruins dentro da função (minha falha). Portanto, também pode haver um BadArgumentError para distinguir erros de valor nos argumentos dos erros de valor nos locais.

BobHy
fonte
Eu aumentaria KeyErrorpara a palavra-chave não encontrada (já que uma palavra-chave explícita ausente é semanticamente idêntica a um **kwargsditado que está faltando essa chave).
cowbert