Qual é a maneira correta de declarar classes de exceção personalizadas no Python moderno? Meu principal objetivo é seguir o padrão de outras classes de exceção, para que (por exemplo) qualquer sequência extra que eu inclua na exceção seja impressa por qualquer ferramenta capturada pela exceção.
Por "Python moderno", quero dizer algo que será executado no Python 2.5, mas será "correto" para a maneira de fazer as coisas do Python 2.6 e do Python 3. *. Por "personalizado", quero dizer um objeto Exception que pode incluir dados extras sobre a causa do erro: uma string, talvez também algum outro objeto arbitrário relevante para a exceção.
Fui enganado pelo seguinte aviso de reprovação no Python 2.6.2:
>>> class MyError(Exception):
... def __init__(self, message):
... self.message = message
...
>>> MyError("foo")
_sandbox.py:3: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
Parece loucura que BaseException
tem um significado especial para os atributos nomeados message
. Pelo PEP-352, entendo que esse atributo tinha um significado especial no 2.5 que eles estão tentando depreciar, então acho que esse nome (e somente esse) agora é proibido? Ugh.
Também estou ciente de que Exception
tem algum parâmetro mágico args
, mas nunca soube usá-lo. Nem tenho certeza de que é o caminho certo para fazer as coisas daqui para frente; muitas discussões que encontrei on-line sugeriam que eles estavam tentando acabar com os argumentos do Python 3.
Atualização: duas respostas sugeriram a substituição __init__
e __str__
/ __unicode__
/ __repr__
. Parece muita digitação, é necessário?
fonte
Com exceções Python modernos, você não precisa de abuso
.message
ou substituição.__str__()
ou.__repr__()
ou nada disso. Se tudo o que você deseja é uma mensagem informativa quando sua exceção é gerada, faça o seguinte:Isso dará um retorno final com
MyException: My hovercraft is full of eels
.Se você quiser mais flexibilidade com a exceção, pode passar um dicionário como argumento:
No entanto, obter esses detalhes em um
except
bloco é um pouco mais complicado. Os detalhes são armazenados noargs
atributo, que é uma lista. Você precisaria fazer algo assim:Ainda é possível passar vários itens para a exceção e acessá-los por meio de índices de tupla, mas isso é altamente desencorajado (e foi planejado para desaprovação há algum tempo). Se você precisar de mais do que uma única informação e o método acima não for suficiente para você, você deve subclassificar
Exception
conforme descrito no tutorial .fonte
Exception(foo, bar, qux)
.Isso é bom, a menos que sua exceção seja realmente um tipo de exceção mais específica:
Ou melhor (talvez perfeito), em vez de
pass
dar uma sequência de caracteres:Subclasses de exceção de subclasse
Dos documentos
Isso significa que, se sua exceção for um tipo de exceção mais específica, subclasse essa exceção em vez da genérica
Exception
(e o resultado será o de que você ainda deriva,Exception
conforme recomendado pela documentação). Além disso, você pode pelo menos fornecer uma string de documento (e não ser forçado a usar apass
palavra - chave):Defina os atributos que você mesmo cria com um costume
__init__
. Evite passar um ditado como argumento posicional, os futuros usuários do seu código agradecerão. Se você usar o atributo de mensagem descontinuada, atribuir você mesmo evitaráDeprecationWarning
:Não há realmente nenhuma necessidade de escrever o seu próprio
__str__
ou__repr__
. Os embutidos são muito legais e sua herança cooperativa garante que você o use.Crítica da resposta principal
Novamente, o problema com o exposto acima é que, para capturá-lo, você precisará nomeá-lo especificamente (importando-o se criado em outro lugar) ou capturar Exception (mas você provavelmente não está preparado para lidar com todos os tipos de exceções, e você só deve pegar as exceções que está preparado para lidar). Críticas semelhantes às abaixo, mas, além disso, não é assim que se inicializa via
super
, e você receberá umaDeprecationWarning
se acessar o atributo de mensagem:Também requer exatamente dois argumentos a serem passados (além do
self
.) Nem mais, nem menos. Essa é uma restrição interessante que os usuários futuros podem não apreciar.Ser direto - viola a substituibilidade de Liskov .
Vou demonstrar os dois erros:
Comparado com:
fonte
BaseException.message
se foi no Python 3, então a crítica só vale para versões antigas, certo?__str__
método emMyAppValueError
vez de confiar nomessage
atributoValueError
. Isso faz sentido se estiver na categoria de erros de valor. Se não estiver na categoria de erros de valor, eu argumentaria contra isso na semântica. Há espaço para algumas nuances e raciocínios por parte do programador, mas prefiro especificidade quando aplicável. Vou atualizar minha resposta para melhor abordar o assunto em breve.veja como as exceções funcionam por padrão se um vs mais atributos forem usados (rastreamentos omitidos):
então você pode querer ter uma espécie de " modelo de exceção ", funcionando como uma exceção em si, de maneira compatível:
isso pode ser feito facilmente com esta subclasse
e se você não gostar da representação padrão de tupla, adicione
__str__
método àExceptionTemplate
classe, como:e você terá
fonte
A partir do Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html ), o método recomendado ainda é:
Não se esqueça de documentar por que uma exceção personalizada é necessária!
Se você precisar, este é o caminho a seguir para exceções com mais dados:
e buscá-los como:
payload=None
é importante torná-lo em decapagem. Antes de descartá-lo, você deve ligar paraerror.__reduce__()
. O carregamento funcionará conforme o esperado.Talvez você deva investigar para encontrar uma solução usando a
return
instrução pythons se precisar de muitos dados a serem transferidos para alguma estrutura externa. Isso parece ser mais claro / mais pitônico para mim. Exceções avançadas são muito usadas em Java, que às vezes pode ser irritante ao usar uma estrutura e ter que detectar todos os erros possíveis.fonte
__str__
) e não outras respostas que usamsuper().__init__(...)
. Apenas uma pena que substitui__str__
e__repr__
provavelmente é necessária apenas para uma melhor serialização "padrão".Você deve substituir
__repr__
ou__unicode__
métodos, em vez de usar a mensagem, os argumentos que você fornece ao construir a exceção estarão noargs
atributo do objeto de exceção.fonte
Não, "mensagem" não é proibida. É apenas obsoleto. Seu aplicativo funcionará bem com o uso da mensagem. Mas você pode se livrar do erro de descontinuação, é claro.
Quando você cria classes Exception personalizadas para seu aplicativo, muitas delas não são subclasses apenas de Exception, mas de outras, como ValueError ou similar. Então você tem que se adaptar ao uso de variáveis.
E se você tiver muitas exceções em seu aplicativo, geralmente é uma boa ideia ter uma classe base personalizada comum para todos eles, para que os usuários de seus módulos possam fazer
E nesse caso, você pode fazer o
__init__ and __str__
necessário lá, para não precisar repeti-lo para todas as exceções. Mas simplesmente chamar a variável de mensagem de algo mais do que a mensagem funciona.De qualquer forma, você só precisa
__init__ or __str__
se fizer algo diferente do que a própria exceção faz. E, se a depreciação for necessária, você precisará das duas ou receberá um erro. Não é necessário muito código extra por classe. ;)fonte
Veja um artigo muito bom " O guia definitivo para exceções do Python ". Os princípios básicos são:
BaseException.__init__
com apenas um argumento.Há também informações sobre organização (em módulos) e exceções de empacotamento, recomendo a leitura do guia.
fonte
Always call BaseException.__init__ with only one argument.
Parece uma restrição desnecessária, pois na verdade aceita qualquer número de argumentos.Tente este exemplo
fonte
Para definir corretamente suas próprias exceções, existem algumas práticas recomendadas que você precisa seguir:
Defina uma classe base herdada de
Exception
. Isso permitirá capturar quaisquer exceções relacionadas ao projeto (exceções mais específicas devem herdar dele):Organizar essas classes de exceção em um módulo separado (por exemplo
exceptions.py
) geralmente é uma boa ideia.Para criar uma exceção personalizada, subclasse a classe de exceção base.
Para adicionar suporte a argumentos extras a uma exceção personalizada, defina um
__init__()
método personalizado com um número variável de argumentos. Chame a classe base__init__()
, passando quaisquer argumentos posicionais para ela (lembre-se de queBaseException
/Exception
espera qualquer número de argumentos posicionais ):Para gerar essa exceção com um argumento extra, você pode usar:
Esse design segue o princípio de substituição de Liskov , pois você pode substituir uma instância de uma classe de exceção base por uma instância de uma classe de exceção derivada. Além disso, permite criar uma instância de uma classe derivada com os mesmos parâmetros que o pai.
fonte