Gerar exceção vs. retornar Nenhum nas funções?

87

Qual é a melhor prática em uma função definida pelo usuário em Python: raiseuma exceção ou return None? Por exemplo, tenho uma função que localiza o arquivo mais recente em uma pasta.

def latestpdf(folder):
    # list the files and sort them
    try:
        latest = files[-1]
    except IndexError:
        # Folder is empty.
        return None  # One possibility
        raise FileNotFoundError()  # Alternative
    else:
        return somefunc(latest)  # In my case, somefunc parses the filename

Outra opção é deixar a exceção e tratá-la no código do chamador, mas acho que é mais claro lidar com um do FileNotFoundErrorque com um IndexError. Ou é incorreto levantar novamente uma exceção com um nome diferente?

parcydarks
fonte
3
Eu me inclino a levantar uma exceção, então sou forçado a lidar com a exceção na função de chamada. Se eu esquecer de verificar se a saída é Nenhum na função de chamada, posso ter um bug latente. Se você retornou None, espero que a próxima linha na função de chamada gerará um AttributeError. No entanto, se o valor retornado for adicionado a um dicionário e então 100 chamadas de função em um arquivo de origem diferente, um AttributeError será gerado, você se divertirá procurando por que esse valor era Nenhum.
IceArdor
Em geral, também evito valores que tenham um significado especial ou que tenham várias assinaturas para uma função (ela pode retornar uma string ou Nenhum).
IceArdor

Respostas:

92

É realmente uma questão de semântica. O que isso foo = latestpdf(d) significa ?

É perfeitamente razoável que não haja um arquivo mais recente? Então, claro, basta retornar Nenhum.

Você espera encontrar sempre um arquivo mais recente? Crie uma exceção. E sim, levantar novamente uma exceção mais apropriada é bom.

Se esta for apenas uma função geral que deve ser aplicada a qualquer diretório, eu faria a primeira e retornaria Nenhum. Se o diretório for, por exemplo, destinado a ser um diretório de dados específico que contém um conjunto de arquivos conhecido de um aplicativo, eu levantaria uma exceção.

Eevee
fonte
3
Outro ponto a se considerar: se for levantada uma exceção, uma mensagem pode ser anexada, mas não podemos fazer isso ao retornar None.
kawing-chiu
10

Gostaria de fazer algumas sugestões antes de responder à sua pergunta, pois pode responder à pergunta para você.

  • Sempre nomeie suas funções de forma descritiva. latestpdfsignifica muito pouco para qualquer pessoa, mas examinar sua função latestpdf()obtém o pdf mais recente. Eu sugiro que você nomeie getLatestPdfFromFolder(folder).

Assim que fiz isso ficou claro o que deveria retornar .. Se não houver um pdf levante uma exceção. Mas espere aí mais ..

  • Mantenha as funções claramente definidas. Como não está claro o que somefuc deve fazer e não é (aparentemente) óbvio como ele se relaciona com a obtenção do pdf mais recente, sugiro que você o remova. Isso torna o código muito mais legível.

for folder in folders:
   try:
       latest = getLatestPdfFromFolder(folder)
       results = somefuc(latest)
   except IOError: pass

Espero que isto ajude!

rh0dium
fonte
1
Ou get_latest_pdf_from_folder. Na verdade, Pep8: "Os nomes das funções devem ser minúsculos, com palavras separadas por sublinhados conforme necessário para melhorar a legibilidade."
PatrickT
7

Eu geralmente prefiro lidar com exceções internamente (ou seja, tente / exceto dentro da função chamada, possivelmente retornando um Nenhum) porque o python é digitado dinamicamente. Em geral, considero um julgamento de uma forma ou de outra, mas em uma linguagem digitada dinamicamente, existem pequenos fatores que inclinam a balança a favor de não passar a exceção para o chamador:

  1. Qualquer pessoa que chama sua função não é notificada sobre as exceções que podem ser lançadas. Torna-se uma espécie de arte saber que tipo de exceção você está procurando (e os blocos genéricos, exceto que devem ser evitados).
  2. if val is Noneé um pouco mais fácil do que except ComplicatedCustomExceptionThatHadToBeImportedFromSomeNameSpace. Sério, eu odeio ter que lembrar de digitar from django.core.exceptions import ObjectDoesNotExistno topo de todos os meus arquivos django apenas para lidar com um caso de uso realmente comum. Em um mundo com tipagem estática, deixe o editor fazer isso por você.

Honestamente, porém, é sempre uma chamada de julgamento, e a situação que você está descrevendo, em que a função chamada recebe um erro que não pode ajudar, é um excelente motivo para levantar novamente uma exceção que seja significativa. Você tem a ideia exata, mas a menos que seja uma exceção, fornecerá informações mais significativas em um rastreamento de pilha do que

AttributeError: 'NoneType' object has no attribute 'foo'

que, nove em dez vezes, é o que o chamador verá se você retornar um Nenhum não atendido, não se preocupe.

(Tudo isso me faz desejar que as exceções python tivessem os causeatributos por padrão, como em java, o que permite passar exceções em novas exceções para que você possa relançar tudo o que quiser e nunca perder a fonte original do problema.)

David Berger
fonte
O argumento de que as possíveis exceções não estão definidas e, portanto, são difíceis de entender é um argumento muito válido para Python.
snorberhuis
4

com a digitação do python 3.5 :

função de exemplo ao retornar None será:

def latestpdf(folder: str) -> Union[str, None]

e ao levantar uma exceção será:

def latestpdf(folder: str) -> str 

a opção 2 parece mais legível e pythônica

(+ opção para adicionar comentário à exceção conforme indicado anteriormente.)

Asaf
fonte
5
Union[str, None]deveria serOptional[str]
Georgy
2
uma abreviação, mas você está certo, é mais legível. não está editando, então ambas as opções estão aqui.
Asaf de
1
2 é potencialmente mais legível, mas (infelizmente?) As dicas de tipo não indicam que uma exceção poderia ser lançada. Recentemente, descobri que 1 ajudará a detectar mais erros, pois você é forçado a lidar com um retorno None.
jonespm
2

Em geral, eu diria que uma exceção deve ser lançada se algo catastrófico ocorreu e não pode ser recuperado (ou seja, sua função lida com algum recurso da Internet que não pode ser conectado), e você deve retornar None se sua função realmente deve retornar algo mas nada seria apropriado retornar (ou seja, "Nenhum" se sua função tentar corresponder a uma substring em uma string, por exemplo).


fonte