Python: como posso saber quais exceções podem ser lançadas de uma chamada de método

87

Existe uma maneira de saber (no momento da codificação) quais exceções esperar ao executar o código Python? Eu acabo pegando a classe Exception base 90% do tempo, pois não sei qual tipo de exceção pode ser lançada (e não me diga para ler a documentação. Muitas vezes uma exceção pode ser propagada do fundo. E muitas vezes a documentação não é atualizada ou correta). Existe algum tipo de ferramenta para verificar isso? (como lendo o código python e libs)?

GabiMe
fonte
2
Tenha em mente que em Python <2.6, você também pode raiseusar strings, não apenas BaseExceptionsubclasses. Portanto, se você estiver chamando um código de biblioteca que está fora de seu controle, nem mesmo except Exceptioné suficiente, pois não detectará exceções de string. Como outros apontaram, você está latindo para a árvore errada aqui.
Daniel Pryden
Eu não sabia disso. Eu pensei, exceto Exceção: .. pega quase tudo.
GabiMe
2
except Exceptionfunciona bem para capturar exceções de string no Python 2.6 e posterior.
Jeffrey Harris

Respostas:

22

Acho que uma solução só pode ser imprecisa por causa da falta de regras de digitação estática.

Não estou ciente de alguma ferramenta que verifica exceções, mas você pode criar sua própria ferramenta que atenda às suas necessidades (uma boa chance de brincar um pouco com a análise estática).

Como uma primeira tentativa, você pode escrever uma função que constrói um AST, encontra todos os Raisenós e, em seguida, tenta descobrir padrões comuns de levantamento de exceções (por exemplo, chamar um construtor diretamente)

Seja xo seguinte programa:

x = '''\
if f(x):
    raise IOError(errno.ENOENT, 'not found')
else:
    e = g(x)
    raise e
'''

Crie o AST usando o compilerpacote:

tree = compiler.parse(x)

Em seguida, defina uma Raiseclasse de visitante:

class RaiseVisitor(object):
    def __init__(self):
        self.nodes = []
    def visitRaise(self, n):
        self.nodes.append(n)

E percorra os Raisenós de coleta de AST :

v = RaiseVisitor()
compiler.walk(tree, v)

>>> print v.nodes
[
    Raise(
        CallFunc(
            Name('IOError'),
            [Getattr(Name('errno'), 'ENOENT'), Const('not found')],
            None, None),
        None, None),
    Raise(Name('e'), None, None),
]

Você pode continuar resolvendo símbolos usando tabelas de símbolos do compilador, analisando dependências de dados, etc. Ou você pode apenas deduzir que CallFunc(Name('IOError'), ...)"deve definitivamente significar aumento IOError", o que é bastante bom para resultados práticos rápidos :)

Andrey Vlasovskikh
fonte
Obrigado por esta resposta interessante. Não entendi por que eu deveria procurar algo mais do que todos os nós de aumento. Por que devo "resolver símbolos usando tabelas de símbolos do compilador, analisando dependências de dados"? Não é a única maneira de levantar exceção por raise ()?
GabiMe
1
Dado o v.nodesvalor acima, você não pode realmente dizer o que é Name('IOError')ou Name('e'). Você não sabe que valor (es) esses IOErrore epodem apontar, pois são as chamadas variáveis ​​livres. Mesmo se seu contexto de ligação fosse conhecido (aqui as tabelas de símbolos entram em ação), você deve realizar algum tipo de análise de dependência de dados para inferir seus valores exatos (isso deve ser difícil em Python).
Andrey Vlasovskikh
Como você está procurando uma solução semi-automatizada prática, uma lista de ['IOError(errno.ENOENT, "not found")', 'e']exibidos para o usuário é o suficiente. Mas você não pode inferir classes reais de valores de variáveis ​​representadas por strings :) (desculpe por repostagem)
Andrey Vlasovskikh
1
Sim. Este método, embora inteligente, na verdade não oferece uma cobertura completa. Devido à natureza dinâmica do Python, é perfeitamente possível (embora obviamente uma má ideia) que o código que você está chamando faça algo assim exc_class = raw_input(); exec "raise " + exc_class. A questão é que esse tipo de análise estática não é realmente possível em uma linguagem dinâmica como o Python.
Daniel Pryden
7
A propósito, você pode find /path/to/library -name '*.py' | grep 'raise 'obter apenas resultados semelhantes :)
Andrey Vlasovskikh
24

Você só deve capturar exceções com as quais irá lidar.

Capturar todas as exceções por seus tipos concretos é um absurdo. Você deve capturar exceções específicas que você pode e vai segurar. Para outras exceções, você pode escrever uma captura genérica que captura "Exceção de base", registra-a (usa a str()função) e termina seu programa (ou faz algo mais apropriado em uma situação de falha).

Se você realmente vai lidar com todas as exceções e tem certeza de que nenhuma delas é fatal (por exemplo, se você está executando o código em algum tipo de ambiente em área restrita), então sua abordagem de capturar BaseException genérica se ajusta aos seus objetivos.

Você também pode estar interessado em referência de exceção de linguagem , não uma referência para a biblioteca que você está usando.

Se a referência da biblioteca for realmente pobre e não relançar suas próprias exceções ao capturar as do sistema, a única abordagem útil é executar testes (talvez adicioná-lo ao conjunto de testes, porque se algo não estiver documentado, pode mudar!) . Exclua um arquivo crucial para seu código e verifique qual exceção está sendo lançada. Forneça muitos dados e verifique o erro que isso produz.

Você terá que executar testes de qualquer maneira, já que, mesmo se o método de obter as exceções pelo código-fonte existisse, ele não daria a você nenhuma ideia de como você deve lidar com qualquer um deles . Talvez você devesse exibir a mensagem de erro "Arquivo needful.txt não encontrado!" quando você pega IndexError? Apenas o teste pode dizer.

P Shved
fonte
26
Claro, mas como alguém pode decidir com quais exceções deve lidar se não sabe o que pode ser lançado?
GabiMe
@ bugspy.net, corrigiu minha resposta para refletir este assunto
P Shved
Talvez seja a hora do analisador de código-fonte que pode descobrir isso? Não deve ser muito difícil de desenvolver, eu acho
GabiMe
@ bugspy.net, enfatizei a cláusula de que pode não ser o momento para isso.
P Shved
Claro que você está certo. No entanto, ainda pode ser interessante - durante o desenvolvimento - saber quais tipos de exceções podem ocorrer.
hek2mgl
11

A ferramenta correta para resolver este problema são os testes de unidade. Se você está tendo exceções levantadas por código real que os testes de unidade não geram, então você precisa de mais testes de unidade.

Considere isto

def f(duck):
    try:
        duck.quack()
    except ??? could be anything

pato pode ser qualquer objeto

Obviamente, você pode ter um AttributeErrorpato se não tem charlatão, um TypeErrorpato se tem um charlatão, mas não pode ser chamado. Você não tem ideia do que duck.quack()pode aumentar, talvez até mesmo um DuckErrorou algo

Agora, suponha que você tenha um código como este

arr[i] = get_something_from_database()

Se surgir um, IndexErrorvocê não saberá se veio de arr [i] ou de dentro da função de banco de dados. geralmente não importa muito onde ocorreu a exceção, mas sim que algo deu errado e o que você queria que acontecesse não aconteceu.

Uma técnica útil é capturar e talvez aumentar novamente a exceção como esta

except Exception as e
    #inspect e, decide what to do
    raise
John La Rooy
fonte
Por que pegá-lo se você vai "aumentar novamente"?
Tarnay Kálmán
Você não precisa aumentá-lo novamente, é isso que o comentário deveria indicar.
John La Rooy
2
Você também pode optar por registrar a exceção em algum lugar e, em seguida, aumentá-la novamente
John La Rooy,
2
Não acho que escrever testes de unidade seja a resposta. A questão é "como posso descobrir quais exceções esperar" e escrever testes de unidade não ajudará você a descobrir isso. Na verdade, para escrever o teste de unidade, você já precisa saber quais exceções esperar para escrever um teste de unidade correto, você também precisa responder à questão original.
Bruno Ranschaert
6

Ninguém explicou até agora porque você não pode ter uma lista de exceções completa e 100% correta, então achei que vale a pena comentar. Um dos motivos é uma função de primeira classe. Digamos que você tenha uma função como esta:

def apl(f,arg):
   return f(arg)

Agora aplpode gerar qualquer exceção que fsurgir. Embora não existam muitas funções como essa na biblioteca central, qualquer coisa que use a compreensão de lista com filtros personalizados, mapear, reduzir, etc. é afetada.

A documentação e os analisadores de origem são as únicas fontes "sérias" de informações aqui. Basta ter em mente o que eles não podem fazer.

viraptor
fonte
4

Eu encontrei isso ao usar o socket, eu queria descobrir todas as condições de erro que eu encontraria (então, em vez de tentar criar erros e descobrir qual socket eu queria apenas uma lista concisa). No final, acabei pedindo "/usr/lib64/python2.4/test/test_socket.py" para "aumentar":

$ grep raise test_socket.py
Any exceptions raised by the clients during their tests
        raise TypeError, "test_func must be a callable function"
    raise NotImplementedError, "clientSetUp must be implemented."
    def raise_error(*args, **kwargs):
        raise socket.error
    def raise_herror(*args, **kwargs):
        raise socket.herror
    def raise_gaierror(*args, **kwargs):
        raise socket.gaierror
    self.failUnlessRaises(socket.error, raise_error,
    self.failUnlessRaises(socket.error, raise_herror,
    self.failUnlessRaises(socket.error, raise_gaierror,
        raise socket.error
    # Check that setting it to an invalid value raises ValueError
    # Check that setting it to an invalid type raises TypeError
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,

Que é uma lista bastante concisa de erros. É claro que isso só funciona caso a caso e depende da precisão dos testes (o que geralmente é). Caso contrário, você precisa praticamente capturar todas as exceções, registrá-las e dissecá-las e descobrir como lidar com elas (o que com o teste de unidade não seria tão difícil).

Kurt
fonte
4
Isso fortalece meu argumento de que o tratamento de exceções em Python é muito problemático, se precisarmos usar grep ou analisadores de origem para lidar com algo tão básico (que por exemplo em java existia desde o primeiro dia. Às vezes, verbosidade é uma coisa boa. Java é prolixo mas pelo menos não há surpresas desagradáveis)
GabiMe
@GabiMe, Não é como se essa habilidade (ou digitação estática em geral) fosse uma solução mágica para prevenir todos os bugs. Java está cheio de surpresas desagradáveis. É por isso que o eclipse trava regularmente.
John La Rooy de
2

Existem duas maneiras que considero informativas. O primeiro, execute o código em iPython, que exibirá o tipo de exceção.

n = 2
str = 'me '
str + 2
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Na segunda maneira, nós nos contentamos em pegar muito e melhoramos com o tempo. Inclua uma tryexpressão em seu código e pegue except Exception as err. Imprima dados suficientes para saber qual exceção foi lançada. À medida que exceções são lançadas, melhore seu código adicionando uma exceptcláusula mais precisa . Quando você sentir que detectou todas as exceções relevantes, remova aquela que inclui tudo. De qualquer forma, é uma boa coisa, porque engole erros de programação.

try:
   so something
except Exception as err:
   print "Some message"
   print err.__class__
   print err
   exit(1)
Rahav
fonte
1

normalmente, você só precisa capturar a exceção em torno de algumas linhas de código. Você não gostaria de colocar toda a sua mainfunção na try exceptcláusula. para cada poucas linhas, você sempre deve agora (ou pode facilmente verificar) que tipo de exceção pode ser levantada.

documentos têm uma lista exaustiva de exceções integradas . não tente exceto aquelas exceções que você não está esperando, elas podem ser tratadas / esperadas no código de chamada.

editar : o que pode ser lançado depende obviamente do que você está fazendo! acessar o elemento aleatório de uma sequência IndexError:, elemento aleatório de um dicionário KeyError, etc.

Apenas tente executar essas poucas linhas no IDLE e causar uma exceção. Mas o teste unitário seria uma solução melhor, naturalmente.

SilentGhost
fonte
1
Isso não responde à minha pergunta simples. Eu não pergunto sobre como projetar meu tratamento de exceção, ou quando ou como capturá-lo. Eu pergunto como descobrir o que pode ser lançado
GabiMe
1
@ bugspy.net: É impossível fazer o que você pede e esta é uma solução alternativa perfeitamente válida.
Daniel Pryden