Podnoszenie wyjątku a zwracanie Brak w funkcjach?

85

Jaka jest lepsza praktyka w funkcji zdefiniowanej przez użytkownika w Pythonie: raisewyjątek czy return None? Na przykład mam funkcję, która znajduje najnowszy plik w folderze.

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

Inną opcją jest pozostawienie wyjątku i obsłużenie go w kodzie wywołującego, ale wydaje mi się, że łatwiej jest zająć się a FileNotFoundErrorniż IndexError. A może to zła forma ponownego zgłaszania wyjątku z inną nazwą?

parcydarks
źródło
3
Skłaniam się do zgłaszania wyjątku, więc jestem zmuszony obsłużyć wyjątek w funkcji wywołującej. Jeśli zapomnę sprawdzić, czy funkcja wywołująca ma wartość Brak, może to oznaczać ukryty błąd. Jeśli zwróciłeś None, miejmy nadzieję, że następna linia funkcji wywołującej zgłosi błąd AttributeError. Jeśli jednak zwrócona wartość zostanie dodana do słownika, a następnie 100 wywołań funkcji w innym pliku źródłowym zostanie wywołane AttributeError, będziesz się dobrze bawić, szukając przyczyny tej wartości None.
IceArdor
Ogólnie unikam również wartości, które mają specjalne znaczenie lub mają wiele podpisów dla jednej funkcji (może zwrócić ciąg lub Brak).
IceArdor

Odpowiedzi:

91

To naprawdę kwestia semantyki. Co to foo = latestpdf(d) znaczy ?

Czy to całkiem rozsądne, że nie ma najnowszego pliku? W takim razie po prostu zwróć Brak.

Czy spodziewasz się, że zawsze znajdziesz najnowszy plik? Podnieś wyjątek. I tak, ponowne zgłoszenie bardziej odpowiedniego wyjątku jest w porządku.

Jeśli jest to tylko ogólna funkcja, która ma mieć zastosowanie do dowolnego katalogu, zrobiłbym pierwszą i zwrócił Brak. Jeśli katalog ma być np. Określonym katalogiem danych, który zawiera znany zestaw plików aplikacji, zgłosiłbym wyjątek.

Eevee
źródło
3
Kolejna kwestia do rozważenia: jeśli zgłosisz wyjątek, można załączyć wiadomość, ale nie możemy tego zrobić podczas zwracania None.
kawing-chiu
9

Zanim odpowiem na Twoje pytanie, przedstawię kilka sugestii, ponieważ może to odpowiedzieć na pytanie za Ciebie.

  • Zawsze nazwij swoje funkcje opisowe. latestpdfniewiele znaczy dla nikogo, ale przeglądanie funkcji latestpdf()powoduje wyświetlenie najnowszego pliku PDF. Sugerowałbym, żebyś to nazwał getLatestPdfFromFolder(folder).

Jak tylko to zrobiłem, stało się jasne, co powinien zwrócić. Jeśli nie ma pliku PDF, zgłoś wyjątek. Ale poczekaj tam więcej ...

  • Utrzymuj jasno zdefiniowane funkcje. Ponieważ nie jest jasne, co ma zrobić ktośefuc i nie jest (najwyraźniej) oczywiste, jak to się ma do pobrania najnowszego pliku PDF, sugerowałbym, abyś go usunął. Dzięki temu kod jest znacznie bardziej czytelny.

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

Mam nadzieję że to pomoże!

rh0dium
źródło
Lub get_latest_pdf_from_folder. Rzeczywiście, Pep8: „Nazwy funkcji powinny być pisane małymi literami, aw razie potrzeby wyrazy oddzielone podkreśleniem”.
PatrickT
7

Zwykle wolę obsługiwać wyjątki wewnętrznie (tj. Try / except wewnątrz wywoływanej funkcji, prawdopodobnie zwracając None), ponieważ python jest wpisywany dynamicznie. Ogólnie uważam to za wezwanie do oceny w taki czy inny sposób, ale w dynamicznie wpisywanym języku istnieją małe czynniki, które przechylają szalę na korzyść nieprzekazywania wyjątku dzwoniącemu:

  1. Nikt, kto wywołuje twoją funkcję, nie jest powiadamiany o wyjątkach, które mogą zostać wyrzucone. Staje się trochę formą sztuki, aby wiedzieć, jakiego rodzaju wyjątek szukasz (i należy unikać ogólnych z wyjątkiem bloków).
  2. if val is Nonejest trochę łatwiejsze niż except ComplicatedCustomExceptionThatHadToBeImportedFromSomeNameSpace. Poważnie, nienawidzę konieczności pamiętania o wpisywaniu from django.core.exceptions import ObjectDoesNotExistna górze wszystkich moich plików django tylko po to, aby obsłużyć naprawdę powszechny przypadek użycia. W świecie wpisywanym statycznie, pozwól edytorowi zrobić to za Ciebie.

Szczerze mówiąc, zawsze jest to wezwanie do oceny, a sytuacja, którą opisujesz, w której wywoływana funkcja otrzymuje błąd, którego nie może pomóc, jest doskonałym powodem do ponownego zgłoszenia wyjątku, który ma znaczenie. Masz dokładny właściwy pomysł, ale chyba, że ​​jesteś wyjątkiem, dostarczysz bardziej znaczących informacji w śladzie stosu niż

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

co dziewięć razy na dziesięć zobaczy dzwoniący, jeśli zwrócisz nieobsłużone Brak, nie przejmuj się.

(Wszystko to sprawia, że ​​chciałbym, aby wyjątki w Pythonie miały causedomyślnie atrybuty, tak jak w java, co pozwala przekazywać wyjątki do nowych wyjątków, aby można było wrzucić wszystko, co chcesz, i nigdy nie stracić pierwotnego źródła problemu).

David Berger
źródło
Argument, że możliwe wyjątki nie są zdefiniowane i dlatego trudno je złapać, jest bardzo ważnym argumentem dla Pythona.
snorberhuis
4

z pisaniem w Pythonie 3.5 :

Przykładowa funkcja zwracająca None będzie:

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

a podczas zgłaszania wyjątku będzie:

def latestpdf(folder: str) -> str 

opcja 2 wydaje się bardziej czytelna i pytoniczna

(+ opcja dodania komentarza do wyjątku, jak wspomniano wcześniej).

Asaf
źródło
4
Union[str, None]powinno byćOptional[str]
Georgy
2
skrót, ale masz rację, jest bardziej czytelny. nie edytuje, więc obie opcje są tutaj.
Asaf
2 jest potencjalnie bardziej czytelny, ale (niestety?) Wskazówki dotyczące typu nie wskazują, że może zostać zgłoszony wyjątek. Ostatnio odkryłem, że 1 pomoże wykryć więcej błędów, ponieważ jesteś zmuszony obsłużyć zwrot Brak.
jonespm
2

Ogólnie rzecz biorąc, powiedziałbym, że wyjątek powinien zostać wyrzucony, jeśli wydarzyło się coś katastrofalnego, z którego nie można odzyskać (tj. Twoja funkcja zajmuje się jakimś zasobem internetowym, z którym nie można się połączyć) i powinieneś zwrócić None, jeśli twoja funkcja naprawdę powinna coś zwrócić ale nic nie byłoby właściwe do zwrócenia (np. „Brak”, jeśli funkcja próbuje na przykład dopasować podłańcuch w ciągu).


źródło