A configuração
Costumo ter problemas para determinar quando e como usar exceções. Vamos considerar um exemplo simples: suponha que eu esteja copiando uma página da Web, diga " http://www.abevigoda.com/ ", para determinar se Abe Vigoda ainda está vivo. Para fazer isso, basta baixar a página e procurar os horários em que a frase "Abe Vigoda" aparece. Retornamos a primeira aparição, pois isso inclui o status de Abe. Conceitualmente, ficará assim:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Onde parse_abe_status(s)
pega uma sequência da forma "Abe Vigoda é alguma coisa " e retorna a parte " alguma coisa ".
Antes de argumentar que existem maneiras muito melhores e mais robustas de classificar esta página para o status de Abe, lembre-se de que este é apenas um exemplo simples e artificial usado para destacar uma situação comum em que estou.
Agora, onde esse código pode encontrar problemas? Entre outros erros, alguns "esperados" são:
download_page
pode não conseguir baixar a página e lança umIOError
.- O URL pode não apontar para a página correta, ou a página é baixada incorretamente e, portanto, não há ocorrências.
hits
é a lista vazia, então. - A página da web foi alterada, possivelmente fazendo com que nossas suposições sobre a página estivessem erradas. Talvez esperemos 4 menções a Abe Vigoda, mas agora encontramos 5.
- Por alguns motivos,
hits[0]
pode não ser uma sequência do formato "Abe Vigoda é alguma coisa " e, portanto, não pode ser analisada corretamente.
O primeiro caso não é realmente um problema para mim: um IOError
é lançado e pode ser tratado pelo chamador da minha função. Então, vamos considerar os outros casos e como eu posso lidar com eles. Mas primeiro, vamos supor que implementamos parse_abe_status
da maneira mais estúpida possível:
def parse_abe_status(s):
return s[13:]
Ou seja, ele não realiza nenhuma verificação de erro. Agora, para as opções:
Opção 1: Retorno None
Posso dizer ao chamador que algo deu errado ao retornar None
:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
if not hits:
return None
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Se o interlocutor receber None
da minha função, ele deve assumir que não houve menções a Abe Vigoda e, portanto, algo deu errado. Mas isso é bastante vago, certo? E isso não ajuda no caso em hits[0]
que não é o que pensávamos.
Por outro lado, podemos colocar em algumas exceções:
Opção 2: Usando exceções
Se hits
estiver vazio, um IndexError
será lançado quando tentarmos hits[0]
. Mas não se deve esperar que o responsável pela chamada lide com uma IndexError
função lançada por minha função, pois ele não tem ideia de onde isso IndexError
veio; poderia ter sido jogado por find_all_mentions
, pelo que ele sabe. Portanto, criaremos uma classe de exceção personalizada para lidar com isso:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Agora, e se a página for alterada e houver um número inesperado de ocorrências? Isso não é catastrófico, pois o código ainda pode funcionar, mas um chamador pode querer ter um cuidado extra ou registrar um aviso. Então, eu vou lançar um aviso:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Por fim, podemos descobrir que status
não está vivo ou morto. Talvez, por alguma estranha razão, hoje tenha sido comatose
. Então não quero voltar False
, pois isso implica que Abe está morto. O que devo fazer aqui? Lance uma exceção, provavelmente. Mas que tipo? Devo criar uma classe de exceção personalizada?
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
if status not in ['alive', 'dead']:
raise SomeTypeOfError("Status is an unexpected value.")
# he's either alive or dead
return status == "alive"
Opção 3: em algum lugar no meio
Eu acho que o segundo método, com exceções, é preferível, mas não tenho certeza se estou usando exceções corretamente nele. Estou curioso para ver como os programadores mais experientes lidariam com isso.
fonte
A resposta aceita merece ser aceita e responde à pergunta. Escrevo isso apenas para fornecer um pouco de experiência extra.
Um dos credos de Python é: é mais fácil pedir perdão do que permissão. Isso significa que normalmente você apenas faz as coisas e, se espera exceções, as trata. Em vez de fazer os testes antes da mão para garantir que você não receberá uma exceção.
Quero fornecer um exemplo para mostrar o quão dramática é a diferença de mentalidade em C ++ / Java. Um loop for em C ++ normalmente se parece com:
Uma maneira de pensar sobre isso: acessar
myvector[k]
onde k> = myvector.size () causará uma exceção. Assim, você poderia, em princípio, escrever isso (de maneira muito embaraçosa) como uma tentativa de captura.Ou algo parecido. Agora, considere o que está acontecendo em um loop python for:
Como isso está funcionando? O loop for pega o resultado do intervalo (1) e chama iter () nele, pegando um iterador nele.
Em seguida, ele chama em seguida a cada iteração do loop, até ...:
Em outras palavras, um loop for em python é na verdade uma tentativa, exceto disfarçado.
Quanto à questão concreta, lembre-se de que as exceções interrompem a execução normal da função e devem ser tratadas separadamente. No Python, você deve jogá-los livremente sempre que não houver razão para executar o restante do código em sua função e / ou nenhum dos retornos refletir corretamente o que aconteceu na função. Observe que retornar cedo de uma função é diferente: retornar cedo significa que você já descobriu a resposta e não precisa do restante do código para descobrir a resposta. Estou dizendo que exceções devem ser lançadas quando a resposta não é conhecida e o restante do código para determinar a resposta não pode ser razoavelmente executado. Agora, "refletir corretamente", como quais exceções você escolhe lançar, é tudo uma questão de documentação.
No caso de seu código específico, eu diria que qualquer situação que faça com que os hits sejam uma lista vazia deve ser lançada. Por quê? Bem, da maneira como sua função é configurada, não há como determinar a resposta sem analisar os hits. Portanto, se os hits não são analisáveis, porque o URL está ruim ou porque os hits estão vazios, a função não pode responder à pergunta e, de fato, nem mesmo pode tentar.
Nesse caso em particular, eu argumentaria que, mesmo que você consiga analisar e não obtenha uma resposta razoável (viva ou morta), ainda deve jogar. Por quê? Porque, a função retorna um booleano. Retorno Nenhum é muito perigoso para o seu cliente. Se eles fizerem uma verificação se Não, não haverá falha, ela será silenciosamente tratada como Falso. Portanto, seu cliente basicamente sempre terá que fazer uma verificação de Se é Nenhum, de qualquer forma, se ele não quiser falhas silenciosas ... então você provavelmente deve apenas jogar.
fonte
Você deve usar exceções quando algo excepcional ocorrer. Ou seja, algo que não deve ocorrer devido ao uso adequado do aplicativo. Se for permitido e esperado que o consumidor do seu método procure por algo que não será encontrado, "não encontrado" não é um caso excepcional. Nesse caso, você deve retornar nulo ou "Nenhum" ou {}, ou algo que indique um conjunto de retorno vazio.
Se, por outro lado, você realmente espera que os consumidores do seu método sempre encontrem o que está sendo pesquisado (a não ser que eles tenham estragado de alguma maneira), não descobrir que seria uma exceção e você deve seguir com isso.
A chave é que o tratamento de exceções pode ser caro - as exceções devem coletar informações sobre o estado do seu aplicativo quando elas ocorrerem, como um rastreamento de pilha, para ajudar as pessoas a decifrar por que elas ocorreram. Eu não acho que é isso que você está tentando fazer.
fonte
String
ae você escolher "Nenhum" como seu indicador, isso significa que você deve ter cuidado para que "Nenhum" nunca seja um valor válido. Observe também que há uma diferença entre olhar os dados e não encontrar um valor e não conseguir recuperá-los, portanto, não podemos encontrá-los. Ter o mesmo resultado para esses dois casos significa que você não tem visibilidade quando não obtém valor quando espera que exista um.Se eu estivesse escrevendo uma função
Eu o escreveria
return True
ouFalse
nos casos em que tenho absoluta certeza de um ou de outro, eraise
um erro em qualquer outro caso (por exemploraise ValueError("Status neither 'dead' nor 'alive'")
). Isso ocorre porque a função que chama minha está esperando um valor booleano, e se eu não puder fornecer isso com certeza, o fluxo regular do programa não deve continuar.Algo como o seu exemplo de obter um número diferente de "hits" do que o esperado, eu provavelmente ignoraria; desde que um dos hits ainda corresponda ao meu padrão "Abe Vigoda está {morto | vivo}", tudo bem. Isso permite que a página seja reorganizada, mas ainda obtém as informações apropriadas.
Ao invés de
Eu verificaria explicitamente:
como isso tende a ser "mais barato" do que configurar o
try
.Eu concordo com você
IOError
; Também não tentaria lidar com o erro ao conectar-me ao site - se não pudermos, por algum motivo, este não for o local apropriado para lidar com ele (pois não nos ajuda a responder à nossa pergunta) e deve passar para a função de chamada.fonte