Który wyjątek powinienem zgłosić w przypadku złych / nielegalnych kombinacji argumentów w Pythonie?

542

Zastanawiałem się nad najlepszymi praktykami wskazywania nieprawidłowych kombinacji argumentów w Pythonie. Spotkałem kilka sytuacji, w których masz taką funkcję:

def import_to_orm(name, save=False, recurse=False):
    """
    :param name: Name of some external entity to import.
    :param save: Save the ORM object before returning.
    :param recurse: Attempt to import associated objects as well. Because you
        need the original object to have a key to relate to, save must be
        `True` for recurse to be `True`.
    :raise BadValueError: If `recurse and not save`.
    :return: The ORM object.
    """
    pass

Jedyną irytacją jest to, że każda paczka ma swoją, zwykle nieco się różni BadValueError. Wiem, że w Javie istnieje java.lang.IllegalArgumentException- czy dobrze wiadomo, że wszyscy będą tworzyć własne BadValueErrorw Pythonie, czy jest też inna, preferowana metoda?

cdleary
źródło

Odpowiedzi:

608

Chciałbym tylko podnieść ValueError , chyba że potrzebujesz bardziej szczegółowego wyjątku.

def import_to_orm(name, save=False, recurse=False):
    if recurse and not save:
        raise ValueError("save must be True if recurse is True")

Naprawdę nie ma sensu tego robić class BadValueError(ValueError):pass- twoja klasa niestandardowa jest identyczna w użyciu z ValueError , więc dlaczego tego nie użyć?

dbr
źródło
65
> „więc dlaczego tego nie użyć?” - specyfika. Być może chcę złapać na jakiejś zewnętrznej warstwie „MyValueError”, ale nie na żadnym „allError”.
Kevin Little,
7
Tak, więc część pytania o specyficzność dotyczy tego, gdzie jeszcze powstaje ValueError. Jeśli funkcja callee lubi twoje argumenty, ale wywołuje math.sqrt (-1) wewnętrznie, wywołujący może wychwycić ValueError, oczekując, że jego argumenty były nieodpowiednie. Może po prostu sprawdzasz wiadomość w tym przypadku ...
cdleary
3
Nie jestem pewien, czy ten argument się utrzymuje: jeśli ktoś dzwoni math.sqrt(-1), to błąd programowy, który i tak należy naprawić. ValueErrornie jest przeznaczony do przechwytywania podczas normalnego wykonywania programu lub może pochodzić RuntimeError.
ereOn
2
Jeśli błąd dotyczy LICZBY argumentów, dla funkcji o zmiennej liczbie argumentów ... na przykład funkcji, w której argumenty muszą być parzystą liczbą argumentów, powinieneś wywołać błąd typu, aby zachować spójność. I nie twórz własnej klasy, chyba że a) masz przypadek użycia lub b) eksportujesz bibliotekę do użytku przez innych. Przedwczesna funkcjonalność to śmierć kodu.
Erik Aronesty,
104

Odziedziczyłbym po ValueError

class IllegalArgumentError(ValueError):
    pass

Czasami lepiej jest tworzyć własne wyjątki, ale dziedziczyć po wbudowanym, który jest tak blisko, jak chcesz.

Jeśli chcesz wyłapać ten konkretny błąd, dobrze jest mieć nazwę.

Markus Jarderot
źródło
26
Przestań pisać zajęcia i niestandardowe wyjątki - pyvideo.org/video/880/stop-writing-classes
Hamish Grubijan
40
@HamishGrubijan to wideo jest okropne. Kiedy ktoś zasugerował dobre wykorzystanie klasy, po prostu beczał: „Nie używaj klas”. Znakomity. Klasy są dobre. Ale nie wierz mi na słowo .
Rob Grant
12
@RobertGrant Nie, nie rozumiesz. W tym filmie tak naprawdę nie chodzi o dosłownie „nie używaj klas”. Chodzi o to, aby nie nadmiernie komplikować rzeczy.
RayLuo
15
@RayLuo możesz sprawdzić poprawność psychiczną tego, co mówi film i przekształcić go w smaczną, sensowną alternatywną wiadomość, ale tak właśnie mówi film, i to właśnie odejdzie ktoś, kto nie ma dużego doświadczenia i zdrowego rozsądku z.
Rob Grant,
3
@SamuelSantana, jak powiedziałem, za każdym razem, gdy ktoś podnosi rękę i pyta „co z X?” gdzie X był dobrym pomysłem, powiedział po prostu: „nie twórz innej klasy”. Całkiem jasne. Zgadzam się, że kluczem jest równowaga; problem polega na tym, że jest to zbyt niejasne, aby żyć :-)
Rob Grant
18

Myślę, że najlepszym sposobem na poradzenie sobie z tym jest sposób, w jaki sam Python sobie z tym radzi. Python podnosi błąd typu. Na przykład:

$ python -c 'print(sum())'
Traceback (most recent call last):
File "<string>", line 1, in <module>
TypeError: sum expected at least 1 arguments, got 0

Nasz młodszy programista właśnie znalazł tę stronę w wyszukiwaniu w Google „niepoprawne argumenty wyjątku Python” i jestem zaskoczony, że oczywista (dla mnie) odpowiedź nigdy nie była sugerowana w ciągu dekady, odkąd zadano to pytanie.

J Bones
źródło
8
Nic mnie nie zaskakuje, ale zgadzam się w 100%, że TypeError jest poprawnym wyjątkiem, jeśli typ jest niepoprawny w przypadku niektórych argumentów przekazanych do funkcji. Błąd ValueError byłby odpowiedni, jeśli zmienne są poprawnego typu, ale ich zawartość i wartości nie mają sensu.
user3504575 24.0419
Myślę, że jest to prawdopodobnie brakujące lub nieuzasadnione argumenty, podczas gdy pytanie dotyczy argumentów, które są podane poprawnie, ale są niepoprawne na wyższym poziomie abstrakcji obejmującym wartość danego argumentu. Ale tak jak szukałem tego pierwszego, więc i tak głosujcie pozytywnie.
Nikt
2
Jak powiedzieli @ user3504575 i @Nobody nikt, TypeError jest używany, jeśli argumenty nie pasują do podpisu funkcji (zła liczba argumentów pozycyjnych, argumenty słów kluczowych o niewłaściwej nazwie, zły typ argumentu), ale błąd ValueError jest używany, gdy wywołanie funkcji pasuje do podpisu, ale wartości argumentów są nieprawidłowe (np. wywołanie int('a')). źródło
goodmami
Ponieważ pytanie OP odnosiło się do „nieprawidłowych kombinacji argumentów”, wydaje się, że błąd typu byłby odpowiedni, ponieważ byłby to przypadek, w którym sygnatura funkcji jest zasadniczo niepoprawna dla przekazywanych argumentów.
J Bones
Twój przykład wywołuje sum()bez argumentów, co oznacza TypeError, ale OP zajmował się „nielegalnymi” kombinacjami wartości argumentów, gdy typy argumentów są poprawne. W tym przypadku oba savei recursesą boolami, ale jeśli recursetak, Trueto savenie powinno być False. Jest to ValueError. Zgadzam się, że odpowiedź na jakąś interpretację tytułu pytania byłaby możliwa TypeError, ale nie w przedstawionym przykładzie.
goodmami,
11

Najczęściej widziałem wbudowane ValueErrorużywane w tej sytuacji.

Eli Courtwright
źródło
8

Zależy to od problemu z argumentami.

Jeśli argument ma niewłaściwy typ, podnieś błąd TypeError. Na przykład, gdy otrzymasz ciąg zamiast jednego z tych booleanów.

if not isinstance(save, bool):
    raise TypeError(f"Argument save must be of type bool, not {type(save)}")

Zauważ jednak, że w Pythonie rzadko przeprowadzamy takie kontrole. Jeśli argument naprawdę jest nieprawidłowy, pewna głębsza funkcja prawdopodobnie narzeka za nas. A jeśli sprawdzimy tylko wartość boolowską, być może jakiś użytkownik kodu po prostu poda jej ciąg znaków, wiedząc, że niepuste ciągi są zawsze Prawdą. Może uratować mu obsadę.

Jeśli argumenty mają niepoprawne wartości, zwiększ wartośćError. Wydaje się to bardziej odpowiednie w twoim przypadku:

if recurse and not save:
    raise ValueError("If recurse is True, save should be True too")

Lub w tym konkretnym przypadku, wartość True rekurencji oznacza True wartość save. Ponieważ uważam to za naprawę po błędzie, możesz również złożyć skargę w dzienniku.

if recurse and not save:
    logging.warning("Bad arguments in import_to_orm() - if recurse is True, so should save be")
    save = True
Gloweye
źródło
Myślę, że to najdokładniejsza odpowiedź. Jest to oczywiście niedoceniane (dotychczas 7 głosów, w tym mój).
Siu Ching Pong -Asuka Kenji-
-1

Nie jestem pewien, zgadzam się z dziedziczenia ValueError- mojej interpretacji dokumentacji jest to, że ValueErrorjest tylko ma być podniesiony przez builtins ... dziedziczenie z nim lub podnosząc ją samemu wydaje się błędne.

Wywoływany, gdy wbudowana operacja lub funkcja odbiera argument, który ma odpowiedni typ, ale niepoprawną wartość, a sytuacja nie jest opisana bardziej precyzyjnym wyjątkiem, takim jak IndexError.

- Dokumentacja ValueError

cdleary
źródło
Porównaj google.com/codesearch?q=lang:python+class \ + \ w Błąd (([^ E] \ w * | E [^ x] \ w )): z google.com/codesearch?q=lang: python + klasa \ + \ w * Błąd (wyjątek):
Markus Jarderot
13
Ten blurb oznacza po prostu, że wbudowane podnoszą go, a nie tylko, że wbudowane mogą go podnieść. W tym przypadku nie byłoby w pełni właściwe, aby dokumentacja Pythona mówiła o tym, co wychodzą biblioteki zewnętrzne.
Ignacio Vazquez-Abrams
5
Każde oprogramowanie Pythona, które kiedykolwiek widziałem, służyło ValueErrordo tego rodzaju rzeczy, więc myślę, że próbujesz wczytywać zbyt wiele w dokumentację.
James Bennett,
6
Err, jeśli zamierzamy użyć wyszukiwań kodu Google do argumentowania tego: google.com/codesearch?q=lang%3Apython+raise%5C+ValueError # 66 300 przypadków podniesienia ValueError, w tym Zope, Xen, Django, Mozilla (i to tylko z pierwszej strony wyników). Jeśli pasuje wyjątek wbudowany, użyj go ..
dbr
7
Jak stwierdzono, dokumentacja jest niejednoznaczna. Powinien zostać zapisany jako „Wywoływany, gdy odbiera wbudowana operacja lub funkcja wbudowana” lub jako „Wywoływany, gdy odbiera funkcja lub operacja wbudowana”. Oczywiście, niezależnie od pierwotnych intencji, obecna praktyka ją pokonała (jak wskazuje @dbr). Dlatego należy go przepisać jako drugi wariant.
Tytułowy
-1

Zgadzam się z sugestią Markusa dotyczącą wprowadzenia własnego wyjątku, ale tekst wyjątku powinien wyjaśniać, że problem dotyczy listy argumentów, a nie poszczególnych wartości argumentów. Zaproponowałbym:

class BadCallError(ValueError):
    pass

Używane, gdy brakuje argumentów słów kluczowych wymaganych dla konkretnego wywołania lub wartości argumentów są indywidualnie ważne, ale niespójne ze sobą. ValueErrornadal miałoby rację, gdy określony argument jest poprawnego typu, ale poza zakresem.

Czy nie powinien to być standardowy wyjątek w Pythonie?

Ogólnie rzecz biorąc, chciałbym, aby styl Pythona był nieco ostrzejszy w odróżnianiu złych danych wejściowych funkcji (błąd wywołującego) od złych wyników w obrębie funkcji (moja wina). Może więc istnieć błąd BadArgumentError, aby odróżnić błędy wartości w argumentach od błędów wartości w plikach lokalnych.

BobHy
źródło
Podbiłbym KeyErrorza nie znaleziono słowa kluczowego (ponieważ brakujące jawne słowo kluczowe jest semantycznie identyczne ze słowem, w **kwargsktórym brakuje tego klucza).
cowbert