Python - aser vs if & return

12

Piszę skrypt, który robi coś z plikiem tekstowym (to, co robi, nie ma znaczenia dla mojego pytania). Więc zanim zrobię coś z plikiem, chcę sprawdzić, czy plik istnieje. Mogę to zrobić, nie ma problemu, ale chodzi bardziej o estetykę.

Oto mój kod, implementujący to samo na dwa różne sposoby.

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.

lub:

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


file does NOT exist.

Pierwsza metoda generuje dane wyjściowe, które są w większości trywialne, jedyne, na czym mi zależy, to że plik nie istnieje.

Druga metoda zwraca ciąg znaków, jest to proste.

Moje pytania brzmią: która metoda jest lepsza, aby poinformować użytkownika, że ​​plik nie istnieje? Zastosowanie tej assertmetody wydaje się w jakiś sposób bardziej pytoniczne.

Vader
źródło

Odpowiedzi:

33

Zamiast tego wybrałbyś trzecią opcję: użycie raisei określony wyjątek. Może to być jeden z wbudowanych wyjątków lub można utworzyć niestandardowy wyjątek dla zadania.

W tym przypadku skorzystałbym IOError, ale ValueErrormoże również pasować:

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

Użycie określonego wyjątku pozwala zgłosić inne wyjątki dla różnych wyjątkowych okoliczności i pozwala dzwoniącemu obsłużyć wyjątek z wdziękiem.

Oczywiście wiele operacji na plikach (takich jak open()) już się podnosi OSError; jawnie pierwsze sprawdzenie, czy plik istnieje, może być tutaj zbędne.

Nie używaj assert; jeśli uruchomisz Pythona z -Oflagą, wszystkie asercje zostaną usunięte z kodu.

Martijn Pieters
źródło
interesujący punkt na temat redundancji! Czy poleciłbyś tego uniknąć? tzn. „i tak się później nie uda”
Ciprian Tomoiagă
1
@ CiprianTomoiagă: po co podwajać testy? Jeśli następna linia modify_file()to with open(filename) as f:, wówczas IOErrorrównież zostanie podniesiona. Nowsze wersje języka Python podają więcej szczegółów w podklasach IOError( FileNotFoundErrorszczególnie przychodzi na myśl), które mogą być pomocne dla programisty używającego tego interfejsu API. Jeśli kod wykona własne kontrole i podniesie, IOErrorwówczas te pomocne szczegóły zostaną utracone.
Martijn Pieters
@MartijnPieters udzieliłby zadowalającej odpowiedzi na pytanie „po co podwajać testy?” być, gdy pierwsze sprawdzenie jest szybsze niż wyjątek zgłoszony, gdy open () kończy się niepowodzeniem? np. gdy sprawdzanie istnienia pliku jest szybsze niż próba otwarcia i ostatecznie tego nie robi.
Marcel Wilson
1
@MarcelWilson: Nie, ponieważ będziesz mikrooptymalizować w stosunku do metody, która wykonuje operacje we / wy. Żadna poprawka w drobnej semantyce Pythona nie przyspieszy operacji we / wy, a jedynie pogorszy czytelność i łatwość konserwacji. Powiedziałbym, że skup się na obszarach o większym wpływie.
Martijn Pieters,
12

assertjest przeznaczony dla przypadków, w których programista wywołujący funkcję popełnił błąd, w przeciwieństwie do użytkownika . Używanie assertw takich okolicznościach pozwala upewnić się, że programiści prawidłowo używają funkcji podczas testowania, ale następnie usunąć ją z produkcji.

Jego wartość jest nieco ograniczona, ponieważ musisz upewnić się, że ćwiczysz tę ścieżkę przez kod, i często chcesz dodatkowo poradzić sobie z problemem za pomocą oddzielnej ifinstrukcji w produkcji. assertjest najbardziej użyteczny w sytuacjach takich jak: „Chcę pomóc obejść ten problem, jeśli użytkownik go dotknie, ale jeśli programista go dotknie, chcę, aby mocno się zawiesił, aby poprawił kod, który wywołuje tę funkcję niepoprawnie”.

W twoim konkretnym przypadku brakujący plik jest prawie na pewno błędem użytkownika i powinien zostać rozwiązany poprzez zgłoszenie wyjątku.

Karl Bielefeldt
źródło
5

From UsingAssertionsEffectective

Sprawdzanie isinstance () nie powinno być nadużywane: jeśli kwacze jak kaczka, być może nie trzeba zbyt głęboko badać, czy tak naprawdę jest. Czasami przydatne może być przekazanie wartości, których nie spodziewał się oryginalny programista.

Miejsca, w których warto rozważyć wprowadzenie twierdzeń:

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 

Chodzi o to, że jeśli coś pójdzie nie tak, chcemy, aby było to jak najszybciej oczywiste.

Łatwiej jest złapać nieprawidłowe dane w punkcie, w którym się znajdują, niż dowiedzieć się, jak je później dostać, gdy powoduje problemy.

Asercje nie zastępują testów jednostkowych ani testów systemowych, ale raczej uzupełnienie. Ponieważ twierdzenia są czystym sposobem na zbadanie wewnętrznego stanu obiektu lub funkcji, zapewniają „za darmo” jasne wsparcie dla testu czarnej skrzynki, który sprawdza zachowanie zewnętrzne.

Asercji nie należy używać do testowania przypadków awarii, które mogą wystąpić z powodu złych danych wejściowych użytkownika lub awarii systemu operacyjnego / środowiska, takich jak brak pliku. Zamiast tego powinieneś zgłosić wyjątek lub wydrukować komunikat o błędzie lub cokolwiek jest odpowiednie. Jednym z ważnych powodów, dla których asercje powinny być używane tylko do autotestów programu, jest to, że asercje można wyłączyć w czasie kompilacji.

Jeśli Python zostanie uruchomiony z opcją -O, asercje zostaną usunięte i nie będą oceniane. Jeśli więc kod intensywnie wykorzystuje asercje, ale ma krytyczne znaczenie dla wydajności, istnieje system umożliwiający ich wyłączenie w kompilacjach wersji. (Ale nie rób tego, chyba że jest to naprawdę konieczne. Naukowo udowodniono, że niektóre błędy pojawiają się tylko wtedy, gdy klient korzysta z maszyny, a my chcemy, aby twierdzenia również tam pomogły :-))

dspjm
źródło