Argparse: argomento obbligatorio "y" se è presente "x"


118

Ho un requisito come segue:

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

per l'argomento prox, utilizzo action = 'store_true' per verificare se è presente o meno. Non ho bisogno di nessuno degli argomenti. Ma, se --prox è impostato, ho bisogno anche di rport e lport. C'è un modo semplice per farlo con argparse senza scrivere codice condizionale personalizzato.

Più codice:

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.')

Collegamento, ma volevo menzionare la mia libreria Joffrey . Ti consente di fare ciò che questa domanda vuole, ad esempio, senza farti convalidare tutto da solo (come nella risposta accettata) o fare affidamento su una scappatoia (come nella seconda risposta più votata).
MI Wright

Per chi arriva qui, un'altra soluzione sorprendente: stackoverflow.com/a/44210638/6045800
Tomerikoo

Risposte:


120

No, non c'è alcuna opzione in argparse per creare insiemi di opzioni mutuamente inclusivi .

Il modo più semplice per affrontare questo problema sarebbe:

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

2
Questo è quello che ho finito per fare
asudhak

20
Grazie per il parser.errormetodo, questo è quello che stavo cercando!
MarSoft

7
non dovresti usare "o"? dopotutto hai bisogno di entrambi gli argomenti if args.prox and (args.lport is None or args.rport is None):
yossiz74

1
Invece di args.lport is None, puoi semplicemente usare not args.lport. Penso che sia un po 'più pitonico.
CGFoX

7
Ciò ti impedirebbe di impostare --lporto --rportsu 0, che potrebbe essere un input valido per il programma.
borntyping

53

Stai parlando di avere argomenti condizionatamente richiesti. Come ha detto @borntyping, puoi controllare l'errore e farlo parser.error(), oppure puoi semplicemente applicare un requisito relativo a --proxquando aggiungi un nuovo argomento.

Una semplice soluzione per il tuo esempio potrebbe essere:

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)

In questo modo requiredriceve Trueo a Falseseconda che l'utente sia utilizzato --prox. Questo garantisce anche che -lporte -rporthanno un comportamento indipendente tra loro.


8
Nota che ArgumentParserpuò essere utilizzato per analizzare argomenti da un elenco diverso da sys.argv, in questo caso non funzionerebbe.
Ballpoint

Anche questo fallirà se --prox=<value>viene utilizzata la sintassi.
fnkr

11

Che ne dici di usare il parser.parse_known_args()metodo e quindi aggiungere gli argomenti --lporte --rportcome argomenti richiesti se --proxè presente.

# 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)

Inoltre, tieni presente che puoi fornire lo spazio dei nomi optsgenerato dopo la prima analisi mentre analizzi gli argomenti rimanenti la seconda volta. In questo modo, alla fine, dopo che tutta l'analisi è terminata, avrai un unico spazio dei nomi con tutte le opzioni.

svantaggi:

  • Se --proxnon è presente, le altre due opzioni dipendenti non sono nemmeno presenti nello spazio dei nomi. Sebbene basato sul tuo caso d'uso, se --proxnon è presente, ciò che accade alle altre opzioni è irrilevante.
  • È necessario modificare il messaggio di utilizzo poiché il parser non conosce la struttura completa
  • --lporte --rportnon comparire nel messaggio di aiuto

5

Usi lportquando proxnon è impostato. In caso contrario, perché non fare lporte rportargomenti di prox? per esempio

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

Ciò consente agli utenti di risparmiare la digitazione. È altrettanto facile da testare if args.prox is not None:come if args.prox:.


1
Per completezza dell'esempio, quando il tuo nargs è> 1, otterrai un elenco nell'argomento analizzato ecc., Che puoi affrontare nei soliti modi. Ad esempio, a,b = args.prox, a = args.prox[0], ecc
Dannid

1

La risposta accettata ha funzionato alla grande per me! Poiché tutto il codice è rotto senza test, ecco come ho testato la risposta accettata. parser.error()non genera un argparse.ArgumentErrorerrore esce invece dal processo. Devi provare SystemExit.

con 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"])

con 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"])

ispirato da: utilizzo di unittest per testare argparse - errori di uscita

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.