Argparse: Erforderliches Argument 'y', wenn 'x' vorhanden ist


117

Ich habe folgende Anforderungen:

./xyifier --prox --lport lport --rport rport

Für das Argument prox verwende ich action = 'store_true', um zu überprüfen, ob es vorhanden ist oder nicht. Ich benötige keines der Argumente. Aber wenn --prox gesetzt ist, benötige ich auch rport und lport. Gibt es eine einfache Möglichkeit, dies mit argparse zu tun, ohne eine benutzerdefinierte bedingte Codierung zu schreiben?

Mehr Code:

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', type=int, help='Listen Port.')
non_int.add_argument('--rport', type=int, help='Proxy port.')

Einstecken, aber ich wollte meine Bibliothek joffrey erwähnen . Ermöglicht es Ihnen, beispielsweise das zu tun, was diese Frage will, ohne dass Sie alles selbst validieren (wie in der akzeptierten Antwort) oder sich auf einen Lücken-Hack verlassen (wie in der zweithöchsten Antwort).
MI Wright

Für alle, die hier ankommen, eine weitere erstaunliche Lösung: stackoverflow.com/a/44210638/6045800
Tomerikoo

Antworten:


119

Nein, es gibt in argparse keine Option, um sich gegenseitig einschließende Optionssätze zu erstellen .

Der einfachste Weg, damit umzugehen, wäre:

if args.prox and (args.lport is None or args.rport is None):
    parser.error("--prox requires --lport and --rport.")

2
Das ist, was ich am Ende getan habe
Asudhak

20
Danke für die parser.errorMethode, das habe ich gesucht!
MarSoft

7
solltest du nicht 'oder' verwenden? Immerhin benötigen Sie beide if args.prox and (args.lport is None or args.rport is None):
Argumente

1
Stattdessen args.lport is Nonekönnen Sie einfach verwenden not args.lport. Ich denke, es ist etwas pythonischer.
CGFoX

7
Das würde Sie davon abhalten, --lportoder --rportzu setzen 0, was eine gültige Eingabe für das Programm sein könnte.
Borntyping

53

Sie sprechen von bedingt erforderlichen Argumenten. Wie @borntyping sagte, können Sie nach dem Fehler suchen und dies tun parser.error(), oder Sie können einfach eine Anforderung anwenden, die sich auf --proxdas Hinzufügen eines neuen Arguments bezieht .

Eine einfache Lösung für Ihr Beispiel könnte sein:

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', required='--prox' in sys.argv, type=int)
non_int.add_argument('--rport', required='--prox' in sys.argv, type=int)

Dieser Weg requiredempfängt entweder Trueoder Falseabhängig davon, ob der Benutzer verwendet wird --prox. Dies garantiert auch, dass -lportund -rportein unabhängiges Verhalten untereinander haben.


8
Beachten Sie, dass ArgumentParserdies zum Parsen von Argumenten aus einer anderen Liste als verwendet werden sys.argvkann. In diesem Fall würde dies fehlschlagen.
BallpointBen

Auch dies schlägt fehl, wenn die --prox=<value>Syntax verwendet wird.
fnkr

11

Wie wäre es, wenn Sie die parser.parse_known_args()Methode verwenden und dann die Argumente --lportund --rportargs nach Bedarf hinzufügen, falls --proxvorhanden.

# just add --prox arg now
non_int = argparse.ArgumentParser(description="stackoverflow question", 
                                  usage="%(prog)s [-h] [--prox --lport port --rport port]")
non_int.add_argument('--prox', action='store_true', 
                     help='Flag to turn on proxy, requires additional args lport and rport')
opts, rem_args = non_int.parse_known_args()
if opts.prox:
    non_int.add_argument('--lport', required=True, type=int, help='Listen Port.')
    non_int.add_argument('--rport', required=True, type=int, help='Proxy port.')
    # use options and namespace from first parsing
    non_int.parse_args(rem_args, namespace = opts)

Beachten Sie auch, dass Sie den optsnach dem ersten Parsen generierten Namespace angeben können, während Sie die verbleibenden Argumente beim zweiten Parsen analysieren. Auf diese Weise haben Sie am Ende nach Abschluss der Analyse einen einzigen Namespace mit allen Optionen.

Nachteile:

  • Wenn --proxnicht vorhanden, sind die beiden anderen abhängigen Optionen nicht einmal im Namespace vorhanden. Obwohl basierend auf Ihrem Anwendungsfall, --proxist das, was mit den anderen Optionen passiert, irrelevant , wenn es nicht vorhanden ist.
  • Die Verwendungsnachricht muss geändert werden, da der Parser die vollständige Struktur nicht kennt
  • --lportund --rportnicht in der Hilfemeldung angezeigt werden

5

Verwenden Sie, lportwenn proxnicht eingestellt ist. Wenn nicht, warum nicht machen lportund rportargumentieren prox? z.B

parser.add_argument('--prox', nargs=2, type=int, help='Prox: listen and proxy ports')

Das erspart Ihren Benutzern das Tippen. Es ist genauso einfach zu testen if args.prox is not None:wie if args.prox:.


1
Der Vollständigkeit halber erhalten Sie, wenn Ihre Nargs> 1 sind, eine Liste im analysierten Argument usw., die Sie auf die übliche Weise ansprechen können. ZB a,b = args.prox, a = args.prox[0]usw.
Dannid

1

Die akzeptierte Antwort hat bei mir sehr gut funktioniert! Da der gesamte Code ohne Tests fehlerhaft ist, habe ich hier die akzeptierte Antwort getestet. parser.error()löst keinen argparse.ArgumentErrorFehler aus, sondern beendet den Prozess. Sie müssen testen SystemExit.

mit pytest

import pytest
from . import parse_arguments  # code that rasises parse.error()


def test_args_parsed_raises_error():
    with pytest.raises(SystemExit):
        parse_arguments(["argument that raises error"])

mit unittests

from unittest import TestCase
from . import parse_arguments  # code that rasises parse.error()

class TestArgs(TestCase):

    def test_args_parsed_raises_error():
        with self.assertRaises(SystemExit) as cm:
            parse_arguments(["argument that raises error"])

inspiriert von: Verwenden von unittest zum Testen von Argparse - Beenden von Fehlern

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.