O consenso geral "não use exceções!" Vem principalmente de outros idiomas e até mesmo algumas vezes está desatualizado.
No C ++, lançar uma exceção é muito caro devido ao “desenrolar da pilha”. Toda declaração de variável local é como uma with
declaração em Python, e o objeto nessa variável pode executar destruidores. Esses destruidores são executados quando uma exceção é lançada, mas também ao retornar de uma função. Esse "idioma do RAII" é um recurso de linguagem integral e é super importante escrever código correto e robusto - portanto, o RAII versus as exceções baratas foi uma desvantagem que o C ++ decidiu pelo RAII.
No C ++ inicial, muitos códigos não eram gravados de maneira segura para exceções: a menos que você realmente use RAII, é fácil vazar memória e outros recursos. Portanto, lançar exceções tornaria esse código incorreto. Isso não é mais razoável, pois mesmo a biblioteca padrão C ++ usa exceções: você não pode fingir que não existem. No entanto, as exceções ainda são um problema ao combinar o código C com o C ++.
Em Java, toda exceção tem um rastreamento de pilha associado. O rastreamento da pilha é muito valioso ao depurar erros, mas desperdiça esforço quando a exceção nunca é impressa, por exemplo, porque foi usada apenas para o fluxo de controle.
Portanto, nesses idiomas, as exceções são "muito caras" para serem usadas como fluxo de controle. No Python, isso é menos problemático e as exceções são muito mais baratas. Além disso, a linguagem Python já sofre com alguma sobrecarga que torna o custo das exceções imperceptíveis em comparação com outras construções de fluxo de controle: por exemplo, verificar se existe uma entrada dict com um teste de associação explícito if key in the_dict: ...
geralmente é exatamente tão rápido quanto simplesmente acessar a entrada the_dict[key]; ...
e verificar se você obtenha um KeyError. Alguns recursos de linguagem integral (por exemplo, geradores) são projetados em termos de exceções.
Portanto, embora não haja razão técnica para evitar especificamente exceções no Python, ainda há a questão de se você deve usá-las em vez dos valores de retorno. Os problemas no nível do design, com exceções, são:
eles não são de todo óbvios. Você não pode olhar facilmente para uma função e ver quais exceções podem ser lançadas, portanto nem sempre sabe o que capturar. O valor de retorno tende a ser mais bem definido.
As exceções são o fluxo de controle não local, o que complica seu código. Quando você lança uma exceção, não sabe onde o fluxo de controle será retomado. Para erros que não podem ser tratados imediatamente, essa é provavelmente uma boa idéia, ao notificar o chamador sobre uma condição, isso é totalmente desnecessário.
A cultura Python geralmente é inclinada em favor de exceções, mas é fácil exagerar. Imagine uma list_contains(the_list, item)
função que verifique se a lista contém um item igual a esse item. Se o resultado for comunicado por meio de exceções, é absolutamente irritante, porque temos que chamá-lo assim:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
Retornar um bool seria muito mais claro:
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
Se a função já deve retornar um valor, retornar um valor especial para condições especiais é bastante suscetível a erros, porque as pessoas esquecem de verificar esse valor (provavelmente é a causa de 1/3 dos problemas em C). Uma exceção é geralmente mais correta.
Um bom exemplo é uma pos = find_string(haystack, needle)
função que procura a primeira ocorrência da needle
string na string `haystack e retorna a posição inicial. Mas e se a corda do palheiro não contiver a agulha?
A solução de C e imitada pelo Python é retornar um valor especial. Em C, este é um ponteiro nulo; em Python, esse é -1
. Isso levará a resultados surpreendentes quando a posição for usada como um índice de string sem verificação, especialmente como -1
é um índice válido no Python. Em C, seu ponteiro NULL fornecerá pelo menos um segfault.
No PHP, um valor especial de um tipo diferente é retornado: o booleano em FALSE
vez de um número inteiro. Acontece que isso não é realmente melhor devido às regras implícitas de conversão da linguagem (mas observe que no Python também os booleanos podem ser usados como ints!). Funções que não retornam um tipo consistente são geralmente consideradas muito confusas.
Uma variante mais robusta seria lançar uma exceção quando a cadeia não puder ser encontrada, o que garante que durante o fluxo de controle normal seja impossível usar acidentalmente o valor especial no lugar de um valor comum:
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
Como alternativa, sempre é possível retornar um tipo que não pode ser usado diretamente, mas que primeiro deve ser desembrulhado, por exemplo, uma tupla de resultado-bool em que o booleano indica se ocorreu uma exceção ou se o resultado é utilizável. Então:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
Isso obriga a lidar com problemas imediatamente, mas fica irritante muito rapidamente. Também impede que você encadeie a função facilmente. Toda chamada de função agora precisa de três linhas de código. Golang é uma linguagem que acha que esse incômodo vale a pena.
Então, para resumir, as exceções não são totalmente isentas de problemas e podem definitivamente ser superutilizadas, especialmente quando substituem um valor de retorno "normal". Porém, quando usado para sinalizar condições especiais (não necessariamente apenas erros), as exceções podem ajudá-lo a desenvolver APIs limpas, intuitivas, fáceis de usar e difíceis de mau uso.
collections.defaultdict
oumy_dict.get(key, default)
torna o código muito mais claro do quetry: my_dict[key] except: return default
NÃO! - não em geral - as exceções não são consideradas boas práticas de controle de fluxo, com exceção da única classe de código. O único lugar em que as exceções são consideradas uma maneira razoável ou até melhor de sinalizar uma condição são as operações do gerador ou do iterador. Essas operações podem retornar qualquer valor possível como resultado válido, portanto, é necessário um mecanismo para sinalizar um acabamento.
Considere ler um arquivo binário do fluxo, um byte de cada vez - absolutamente qualquer valor é um resultado potencialmente válido, mas ainda precisamos sinalizar o final do arquivo. Portanto, temos uma escolha, retorne dois valores (o valor do byte e um sinalizador válido), sempre ou crie uma exceção quando não houver mais o que fazer. Nos dois casos, o código de consumo pode se parecer com:
alternativamente:
Mas isso, desde que o PEP 343 foi implementado e portado, tudo foi cuidadosamente incluído na
with
declaração. O acima se torna, o próprio python:Em python3 isso se tornou:
Peço fortemente que você leia PEP 343, que fornece os antecedentes, justificativas, exemplos etc.
Também é comum usar uma exceção para sinalizar o final do processamento ao usar as funções do gerador para sinalizar o final.
Gostaria de acrescentar que seu exemplo de pesquisador é quase certamente reverso; essas funções devem ser geradoras, retornando a primeira correspondência na primeira chamada, depois chamadas substituintes retornando a próxima correspondência e gerando uma
NotFound
exceção quando não houver mais correspondências.fonte
if
seria melhor. Quando se trata de depuração e teste ou mesmo de raciocínio sobre o comportamento dos seus códigos, é melhor não confiar em exceções - elas devem ser a exceção. Eu vi, no código de produção, um cálculo que retornou por meio de um throw / catch em vez de um retorno simples, o que significa que, quando o cálculo atinge um erro de divisão por zero, ele retorna um valor aleatório em vez de falhar, quando o erro aconteceu, causa várias horas de depuração.