Welche Ausnahme sollte ich für schlechte / illegale Argumentkombinationen in Python auslösen?

544

Ich habe mich über die Best Practices für die Angabe ungültiger Argumentkombinationen in Python gewundert. Ich bin auf einige Situationen gestoßen, in denen Sie eine Funktion wie diese haben:

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

Das einzige Ärgernis dabei ist, dass jedes Paket sein eigenes hat, normalerweise etwas anders BadValueError. Ich weiß, dass es in Java gibt java.lang.IllegalArgumentException- ist es gut verstanden, dass jeder seine eigenen BadValueErrorin Python erstellen wird, oder gibt es eine andere, bevorzugte Methode?

cdleary
quelle

Antworten:

608

Ich würde nur ValueError auslösen , es sei denn, Sie benötigen eine spezifischere Ausnahme.

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

Es macht wirklich keinen Sinn, dies zu tun class BadValueError(ValueError):pass- Ihre benutzerdefinierte Klasse ist in der Verwendung mit ValueError identisch. Warum also nicht?

dbr
quelle
65
> "Warum also nicht das benutzen?" - Spezifität. Vielleicht möchte ich an einer äußeren Ebene "MyValueError" fangen, aber nicht an einem / allen "ValueError".
Kevin Little
7
Ja, ein Teil der Frage der Spezifität ist also, wo sonst ValueError aufgeworfen wird. Wenn die Angerufene-Funktion Ihre Argumente mag, aber math.sqrt (-1) intern aufruft, kann ein Aufrufer ValueError abfangen und erwarten, dass seine Argumente unangemessen waren. Vielleicht überprüfen Sie einfach die Nachricht in diesem Fall ...
cdleary
3
Ich bin mir nicht sicher, ob dieses Argument zutrifft: Wenn jemand anruft math.sqrt(-1), ist das ein Programmierfehler, der sowieso behoben werden muss. ValueErrorist nicht dazu gedacht, in der normalen Programmausführung gefangen zu werden, oder es würde von abgeleitet werden RuntimeError.
Am
2
Wenn sich der Fehler auf die ANZAHL der Argumente bezieht, für eine Funktion mit einer variablen Anzahl von Argumenten ... zum Beispiel für eine Funktion, bei der die Argumente eine gerade Anzahl von Argumenten sein müssen, sollten Sie einen TypeError auslösen, um konsistent zu sein. Und machen Sie keine eigene Klasse, es sei denn, a) Sie haben einen Anwendungsfall oder b) Sie exportieren die Bibliothek, um sie von anderen zu verwenden. Vorzeitige Funktionalität ist der Tod von Code.
Erik Aronesty
104

Ich würde von erben ValueError

class IllegalArgumentError(ValueError):
    pass

Manchmal ist es besser, eigene Ausnahmen zu erstellen, aber von einer integrierten zu erben, die so nah wie möglich an Ihren Wünschen liegt.

Wenn Sie diesen bestimmten Fehler abfangen müssen, ist es hilfreich, einen Namen zu haben.

Markus Jarderot
quelle
26
Hören
Hamish Grubijan
40
@ HamishGrubijan das Video ist schrecklich. Wenn jemand eine gute Verwendung einer Klasse vorschlug, meckerte er nur "Verwenden Sie keine Klassen". Brillant. Der Unterricht ist gut. Aber nimm mein Wort nicht dafür .
Rob Grant
12
@ RobertGrant Nein, du verstehst es nicht. In diesem Video geht es nicht wirklich darum, "keine Klassen zu verwenden". Es geht darum, Dinge nicht zu komplizieren.
RayLuo
15
@RayLuo Sie haben möglicherweise die Vernunft überprüft, was das Video sagt, und es in eine schmackhafte, vernünftige alternative Nachricht umgewandelt, aber genau das sagt das Video, und es ist das, was jemand, der nicht viel Erfahrung und gesunden Menschenverstand hat, wegbringen wird mit.
Rob Grant
3
@SamuelSantana wie gesagt, jedes Mal, wenn jemand seine Hand hob und sagte "Was ist mit X?" Wo X eine gute Idee war, sagte er nur: "Mach keine weitere Klasse." Ziemlich klar. Ich bin damit einverstanden, dass der Schlüssel das Gleichgewicht ist. Das Problem ist, dass das einfach zu vage ist, um tatsächlich zu leben :-)
Rob Grant
18

Ich denke, der beste Weg, damit umzugehen, ist die Art und Weise, wie Python selbst damit umgeht. Python löst einen TypeError aus. Zum Beispiel:

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

Unser Junior-Entwickler hat diese Seite gerade in einer Google-Suche nach "falschen Argumenten für Python-Ausnahmen" gefunden, und ich bin überrascht, dass die offensichtliche (für mich) Antwort in dem Jahrzehnt, in dem diese Frage gestellt wurde, nie vorgeschlagen wurde.

J Knochen
quelle
8
Nichts überrascht mich, aber ich stimme zu 100% zu, dass TypeError die richtige Ausnahme ist, wenn der Typ bei einigen der an die Funktion übergebenen Argumente falsch ist. Ein ValueError wäre geeignet, wenn die Variablen vom richtigen Typ sind, ihr Inhalt und ihre Werte jedoch keinen Sinn ergeben.
user3504575
Ich denke, dies ist wahrscheinlich für fehlende oder nicht angeforderte Argumente gedacht, während es sich um Argumente handelt, die korrekt angegeben werden, aber auf einer höheren Abstraktionsebene, die den Wert des angegebenen Arguments beinhaltet, falsch sind . Aber da ich eigentlich nach Ersterem gesucht habe, habe ich trotzdem eine Gegenstimme.
Niemand
2
Wie @ user3504575 und @Nobody sagten, wird TypeError verwendet, wenn die Argumente nicht mit der Funktionssignatur übereinstimmen (falsche Anzahl von Positionsargumenten, Schlüsselwortargumente mit falschem Namen, falscher Argumenttyp), aber beim Funktionsaufruf ein ValueError verwendet wird stimmt mit der Signatur überein, aber die Argumentwerte sind ungültig (z. B. Aufruf int('a') . ). Quelle
Goodmami
Da sich die Frage des OP auf "ungültige Argumentkombinationen" bezog, scheint ein TypeError angemessen zu sein, da dies ein Fall wäre, in dem die Funktionssignatur für die übergebenen Argumente im Wesentlichen falsch ist.
J Bones
Ihr Beispiel ruft sum()ohne Argumente auf, was a ist TypeError, aber das OP befasste sich mit "illegalen" Kombinationen von Argumentwerten, wenn die Argumenttypen korrekt sind. In diesem Fall sind beide saveund recursesind bools, aber wenn recurseist Truedann savesein sollte , nicht False. Dies ist ein ValueError. Ich bin damit einverstanden, dass eine Interpretation des Titels der Frage von beantwortet wird TypeError, jedoch nicht für das vorgestellte Beispiel.
Goodmami
11

Ich habe meistens nur das ValueErrorin dieser Situation verwendete eingebaute gesehen .

Eli Courtwright
quelle
8

Es hängt davon ab, wo das Problem mit den Argumenten liegt.

Wenn das Argument den falschen Typ hat, lösen Sie einen TypeError aus. Zum Beispiel, wenn Sie einen String anstelle eines dieser Booleschen Werte erhalten.

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

Beachten Sie jedoch, dass wir in Python selten solche Überprüfungen durchführen. Wenn das Argument wirklich ungültig ist, wird wahrscheinlich eine tiefere Funktion die Beschwerde für uns erledigen. Und wenn wir nur den booleschen Wert überprüfen, füttert ihn möglicherweise ein Codebenutzer später einfach mit einer Zeichenfolge, da er weiß, dass nicht leere Zeichenfolgen immer True sind. Es könnte ihm eine Besetzung ersparen.

Wenn die Argumente ungültige Werte haben, erhöhen Sie ValueError. Dies scheint in Ihrem Fall angemessener zu sein:

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

Oder in diesem speziellen Fall bedeutet ein wahrer Wert für die Wiederholung einen wahren Wert für das Speichern. Da ich dies als Wiederherstellung nach einem Fehler betrachten würde, möchten Sie sich möglicherweise auch im Protokoll beschweren.

if recurse and not save:
    logging.warning("Bad arguments in import_to_orm() - if recurse is True, so should save be")
    save = True
Gloweye
quelle
Ich denke, das ist die genaueste Antwort. Dies wird offensichtlich unterschätzt (bisher 7 Stimmen einschließlich meiner).
Siu Ching Pong -Asuka Kenji-
-1

Ich bin nicht sicher , ich stimme mit Vererbung aus ValueError- meine Interpretation der Dokumentation ist , dass ValueErrorist nur angeblich von builtins angehoben werden ... von ihm erben oder erhöhen es selbst scheint falsch.

Wird ausgelöst, wenn eine integrierte Operation oder Funktion ein Argument mit dem richtigen Typ, aber einem unangemessenen Wert empfängt und die Situation nicht durch eine genauere Ausnahme wie IndexError beschrieben wird.

- ValueError-Dokumentation

cdleary
quelle
Vergleichen Sie google.com/codesearch?q=lang:python+class \ + \ w Fehler (([^ E] \ w * | E [^ x] \ w )): mit google.com/codesearch?q=lang: Python + Klasse \ + \ w * Fehler (Ausnahme):
Markus Jarderot
13
Dieser Klappentext bedeutet einfach, dass Einbauten ihn anheben und nicht, dass nur Einbauten ihn anheben können. In diesem Fall wäre es nicht völlig angemessen, in der Python-Dokumentation darüber zu sprechen, was externe Bibliotheken auslösen.
Ignacio Vazquez-Abrams
5
Jede Python-Software, die ich je gesehen habe, wurde ValueErrorfür diese Art von Dingen verwendet. Ich denke, Sie versuchen, zu viel in die Dokumentation einzulesen.
James Bennett
6
Ähm, wenn wir Google Code-Suchanfragen verwenden, um dies zu argumentieren: google.com/codesearch?q=lang%3Apython+raise%5C+ValueError # 66.300 Fälle von ValueError, einschließlich Zope, xen, Django, Mozilla (und das ist nur von der ersten Seite der Ergebnisse). Wenn eine eingebaute Ausnahme passt, verwenden Sie sie ..
dbr
7
Wie bereits erwähnt, ist die Dokumentation nicht eindeutig. Es sollte entweder als "ausgelöst, wenn eine integrierte Operation oder eine integrierte Funktion empfangen wird" oder als "ausgelöst, wenn eine Funktion oder eine integrierte Operation empfangen wird" geschrieben worden sein. Unabhängig von der ursprünglichen Absicht hat die derzeitige Praxis sie natürlich übertroffen (wie @dbr hervorhebt). Es sollte also als zweite Variante umgeschrieben werden.
Gleichnamiger
-1

Stimmen Sie dem Vorschlag von Markus zu, Ihre eigene Ausnahme zu rollen, aber der Text der Ausnahme sollte klarstellen, dass das Problem in der Argumentliste und nicht in den einzelnen Argumentwerten enthalten ist. Ich würde vorschlagen:

class BadCallError(ValueError):
    pass

Wird verwendet, wenn Schlüsselwortargumente fehlen, die für den jeweiligen Aufruf erforderlich waren, oder wenn Argumentwerte einzeln gültig sind, aber nicht miteinander übereinstimmen. ValueErrorwäre immer noch richtig, wenn ein bestimmtes Argument vom richtigen Typ ist, aber außerhalb des Bereichs liegt.

Sollte dies nicht eine Standardausnahme in Python sein?

Im Allgemeinen möchte ich, dass der Python-Stil etwas schärfer darin ist, schlechte Eingaben in eine Funktion (Fehler des Aufrufers) von schlechten Ergebnissen innerhalb der Funktion (mein Fehler) zu unterscheiden. Daher kann es auch einen BadArgumentError geben, um Wertfehler in Argumenten von Wertfehlern in Einheimischen zu unterscheiden.

BobHy
quelle
Ich würde KeyErrorfür nicht gefundenes Schlüsselwort erhöhen (da ein fehlendes explizites Schlüsselwort semantisch mit einem **kwargsDiktat identisch ist, bei dem dieser Schlüssel fehlt).
Cowbert