Python argparse gruppo mutuo esclusivo


92

Quello di cui ho bisogno è:

pro [-a xxx | [-b yyy -c zzz]]

Ho provato questo ma non funziona. Qualcuno potrebbe aiutarmi?

group= parser.add_argument_group('Model 2')
group_ex = group.add_mutually_exclusive_group()
group_ex.add_argument("-a", type=str, action = "store", default = "", help="test")
group_ex_2 = group_ex.add_argument_group("option 2")
group_ex_2.add_argument("-b", type=str, action = "store", default = "", help="test")
group_ex_2.add_argument("-c", type=str, action = "store", default = "", help="test")

Grazie!



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

Risposte:


110

add_mutually_exclusive_groupnon fa escludere a vicenda un intero gruppo. Rende le opzioni all'interno del gruppo che si escludono a vicenda.

Quello che stai cercando sono i sottocomandi . Invece di prog [-a xxxx | [-b yyy -c zzz]], avresti:

prog 
  command 1 
    -a: ...
  command 2
    -b: ...
    -c: ...

Per invocare con il primo set di argomenti:

prog command_1 -a xxxx

Per invocare con il secondo set di argomenti:

prog command_2 -b yyyy -c zzzz

È inoltre possibile impostare gli argomenti del comando secondario come posizionali.

prog command_1 xxxx

Un po 'come git o svn:

git commit -am
git merge develop

Esempio di lavoro

# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='help for foo arg.')
subparsers = parser.add_subparsers(help='help for subcommand')

# create the parser for the "command_1" command
parser_a = subparsers.add_parser('command_1', help='command_1 help')
parser_a.add_argument('a', type=str, help='help for bar, positional')

# create the parser for the "command_2" command
parser_b = subparsers.add_parser('command_2', help='help for command_2')
parser_b.add_argument('-b', type=str, help='help for b')
parser_b.add_argument('-c', type=str, action='store', default='', help='test')

Provalo

>>> parser.print_help()
usage: PROG [-h] [--foo] {command_1,command_2} ...

positional arguments:
  {command_1,command_2}
                        help for subcommand
    command_1           command_1 help
    command_2           help for command_2

optional arguments:
  -h, --help            show this help message and exit
  --foo                 help for foo arg.
>>>

>>> parser.parse_args(['command_1', 'working'])
Namespace(a='working', foo=False)
>>> parser.parse_args(['command_1', 'wellness', '-b x'])
usage: PROG [-h] [--foo] {command_1,command_2} ...
PROG: error: unrecognized arguments: -b x

In bocca al lupo.


Li ho già inseriti in un gruppo di argomenti. Come posso aggiungere un sottocomando in questo caso? Grazie!
Sean

1
Aggiornato con codice di esempio. Non userete gruppi, ma subparser.
Jonathan

7
Ma come faresti ciò che OP originariamente chiesto? Al momento ho una serie di sottocomandi, ma uno di quei sottocomandi necessita della possibilità di scegliere tra[[-a <val>] | [-b <val1> -c <val2>]]
code_dredd

3
Questo non risponde alla domanda perché non ti consente di eseguire comandi "noname" e ottenere ciò che OP ha chiesto[-a xxx | [-b yyy -c zzz]]
The Godfather

38

Sebbene la risposta di Jonathan vada perfettamente bene per opzioni complesse, esiste una soluzione molto semplice che funzionerà per i casi semplici, ad esempio 1 opzione esclude altre 2 opzioni come in

command [- a xxx | [ -b yyy | -c zzz ]] 

o anche come nella domanda originale:

pro [-a xxx | [-b yyy -c zzz]]

Ecco come lo farei:

parser = argparse.ArgumentParser()

# group 1 
parser.add_argument("-q", "--query", help="query", required=False)
parser.add_argument("-f", "--fields", help="field names", required=False)

# group 2 
parser.add_argument("-a", "--aggregation", help="aggregation",
                    required=False)

Sto usando qui le opzioni fornite a un wrapper della riga di comando per interrogare un mongodb. L' collectionistanza può chiamare il metodo aggregateo il metodo findcon argomenti opzionaliquery e fields, quindi, si vede perché i primi due argomenti sono compatibili e l'ultimo no.

Quindi ora corro parser.parse_args()e controllo il suo contenuto:

args = parser().parse_args()

print args.aggregation
if args.aggregation and (args.query or args.fields):
    print "-a and -q|-f are mutually exclusive ..."
    sys.exit(2)

Ovviamente, questo piccolo trucco funziona solo per casi semplici e sarebbe un incubo controllare tutte le opzioni possibili se si dispone di molte opzioni e gruppi che si escludono a vicenda. In tal caso dovresti suddividere le tue opzioni in gruppi di comandi come suggerito da Jonathan.


5
Non lo definirei un "trucco" per questo caso, poiché sembra sia più leggibile che gestibile - grazie per averlo segnalato!
saggio

16
Un modo ancora migliore sarebbe quello di utilizzare parser.error("-a and -q ..."). In questo modo verrà stampata automaticamente la guida completa all'uso.
WGH

Tieni presente che in questo caso dovresti anche convalidare i casi come: (1) entrambi qe fsono obbligatori nel primo gruppo è l'utente, (2) uno dei gruppi è richiesto. E questo rende la soluzione "semplice" non più così semplice. Quindi sarei d'accordo che questo è più un trucco per la sceneggiatura artigianale, ma non una vera soluzione
Il Padrino

4

C'è una patch Python (in fase di sviluppo) che ti permetterebbe di farlo.
http://bugs.python.org/issue10984

L'idea è di consentire la sovrapposizione di gruppi che si escludono a vicenda. Quindi usagepotrebbe sembrare:

pro [-a xxx | -b yyy] [-a xxx | -c zzz]

Cambiare il codice argparse in modo da poter creare due gruppi come questo è stata la parte facile. La modifica del usagecodice di formattazione richiedeva la scrittura di un file personalizzato HelpFormatter.

In argparse, i gruppi di azioni non influiscono sull'analisi. Sono solo uno helpstrumento di formattazione. In help, i gruppi che si escludono a vicenda influenzano solo la usagelinea. Durante l'analisi, parserutilizza i gruppi che si escludono a vicenda per costruire un dizionario di potenziali conflitti ( anon può verificarsi con bo c, bnon può verificarsi con a, ecc.) E quindi genera un errore se si verifica un conflitto.

Senza quella patch argparse, penso che la scelta migliore sia testare lo spazio dei nomi prodotto da parse_argste stesso (ad esempio se entrambi ae bhanno valori non predefiniti) e sollevare il tuo errore. Potresti anche usare il meccanismo di errore del parser.

parser.error('custom error message')

1
Problema Python: bugs.python.org/issue11588 sta esplorando modi per consentirti di scrivere test esclusivi / inclusivi personalizzati.
hpaulj
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.