Python - afirmar vs se & retornar

12

Estou escrevendo um script que faz algo em um arquivo de texto (o que ele faz é irrelevante para a minha pergunta). Portanto, antes de fazer algo no arquivo, quero verificar se o arquivo existe. Eu posso fazer isso, não há problema, mas a questão é mais a estética.

Aqui está o meu código, implementando a mesma coisa de duas maneiras diferentes.

def modify_file(filename):
    assert os.path.isfile(filename), 'file does NOT exist.'


Traceback (most recent call last):
  File "clean_files.py", line 15, in <module>
    print(clean_file('tes3t.txt'))
  File "clean_files.py", line 8, in clean_file
    assert os.path.isfile(filename), 'file does NOT exist.'
AssertionError: file does NOT exist.

ou:

def modify_file(filename):
    if not os.path.isfile(filename):
        return 'file does NOT exist.'


file does NOT exist.

O primeiro método produz uma saída que é principalmente trivial, a única coisa que me interessa é que o arquivo não exista.

O segundo método retorna uma string, é simples.

Minhas perguntas são: qual método é melhor para informar ao usuário que o arquivo não existe? Usar o assertmétodo parece de alguma forma mais pitônico.

Vader
fonte

Respostas:

33

Você usaria uma terceira opção: use raisee uma exceção específica. Essa pode ser uma das exceções internas ou você pode criar uma exceção personalizada para o trabalho.

Nesse caso, eu usaria IOError, mas um ValueErrortambém poderia se encaixar:

def modify_file(filename):
    if not os.path.isfile(filename):
        raise IOError('file does NOT exist.')

O uso de uma exceção específica permite gerar outras exceções para diferentes circunstâncias excepcionais e permite que o chamador lide com a exceção normalmente.

Claro, muitas operações de arquivo (como open()) -se elevar OSErrorjá; explicitamente primeiro teste se o arquivo existe pode ser redundante aqui.

Não use assert; se você executar python com o -Osinalizador, todas as asserções serão removidas do código.

Martijn Pieters
fonte
ponto interessante sobre a redundância! Você recomendaria evitá-lo? ou seja, "falhará de qualquer maneira mais tarde"
Ciprian Tomoiagă
1
@ CiprianTomoiagă: por que dobrar os testes? Se a próxima linha de modify_file()é with open(filename) as f:, IOErrortambém seria aumentada. E versões mais recentes do Python forneceram mais detalhes nas subclasses de IOError( FileNotFoundErrorespecificamente vem à mente) que podem ser úteis para um desenvolvedor que usa essa API. Se o código fizer suas próprias verificações e elevações IOError, esses detalhes úteis serão perdidos.
Martijn Pieters
@MartijnPieters seria uma resposta aceitável para "por que dobrar nos testes?" quando a primeira verificação é mais rápida que a exceção gerada quando open () falha? por exemplo, ao verificar a existência de um arquivo, é mais rápido do que tentar abrir e, finalmente, deixar de fazê-lo.
Marcel Wilson
1
@ MarcelWilson: Não, porque você seria micro-otimizado em relação a um método que faz E / S. Nenhuma quantidade de ajustes na semântica minuciosa do Python tornará a E / S mais rápida e prejudicará a legibilidade e a capacidade de manutenção. Concentre-se em áreas com mais impacto, eu diria.
Martijn Pieters
12

assertdestina-se a casos em que o programador que chamou a função cometeu um erro, em oposição ao usuário . O uso assertsob essa circunstância permite garantir que um programador esteja usando sua função corretamente durante o teste, mas depois a retire na produção.

Seu valor é um pouco limitado, já que você precisa garantir que você exerça esse caminho através do código e geralmente deseja lidar com o problema adicionalmente com uma ifdeclaração separada na produção. asserté mais útil em situações como "Desejo solucionar esse problema de maneira útil se um usuário o encontrar, mas se um desenvolvedor o encontrar, quero que ele sofra uma falha grave para que ele corrija o código que chama essa função incorretamente".

No seu caso particular, um arquivo ausente é quase certamente um erro do usuário e deve ser tratado gerando uma exceção.

Karl Bielefeldt
fonte
5

De UsingAssertionsEffectively

A verificação de isinstance () não deve ser usada em demasia: se parecer um pato, talvez não seja necessário investigar profundamente se realmente é. Às vezes, pode ser útil passar valores que não foram antecipados pelo programador original.

Locais a considerar para colocar afirmações:

checking parameter types, classes, or values
checking data structure invariants
checking "can't happen" situations (duplicates in a list, contradictory state variables.)
after calling a function, to make sure that its return is reasonable 

O ponto principal é que, se algo der errado, queremos torná-lo completamente óbvio o mais rápido possível.

É mais fácil capturar dados incorretos no ponto em que entra do que descobrir como chegou mais tarde quando causa problemas.

As afirmações não substituem os testes de unidade ou de sistema, mas um complemento. Como as asserções são uma maneira limpa de examinar o estado interno de um objeto ou função, elas fornecem "de graça" uma ajuda de caixa clara para um teste de caixa preta que examina o comportamento externo.

As asserções não devem ser usadas para testar casos de falha que podem ocorrer devido a entrada incorreta do usuário ou falhas no sistema operacional / ambiente, como um arquivo não encontrado. Em vez disso, você deve gerar uma exceção ou imprimir uma mensagem de erro ou o que for apropriado. Uma razão importante pela qual asserções devem ser usadas apenas para autotestes do programa é que as asserções podem ser desabilitadas em tempo de compilação.

Se o Python for iniciado com a opção -O, as asserções serão removidas e não avaliadas. Portanto, se o código usa muito as asserções, mas é crítico para o desempenho, existe um sistema para desativá-las nas compilações de versão. (Mas não faça isso a menos que seja realmente necessário. Foi cientificamente comprovado que alguns bugs aparecem apenas quando um cliente usa a máquina e queremos que asserções também ajudem lá. :-))

dspjm
fonte