Python argparse: Ustaw wymagany co najmniej jeden argument

96

Używam argparsedo programu Pythona, który może -process, -uploadlub obu:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Program jest bez znaczenia bez przynajmniej jednego parametru. Jak mogę skonfigurować argparsewymuszenie wyboru co najmniej jednego parametru?

AKTUALIZACJA:

Po komentarzach: W jaki sposób Pythonic parametryzuje program z co najmniej jedną opcją?

Adam Matan
źródło
9
-xjest powszechnie flagą i opcjonalną. Wytnij, -jeśli jest to wymagane.
1
Czy nie mógłbyś processustawić zachowania domyślnego (bez potrzeby określania jakichkolwiek opcji) i pozwolić użytkownikowi zmienić to na, uploadjeśli ta opcja jest ustawiona? Zwykle opcje powinny być opcjonalne, stąd nazwa. Należy unikać wymaganych opcji (jest to również w argparse dokumentacji).
Tim Pietzcker,
@AdamMatan Minęły już prawie trzy lata, odkąd zadałeś swoje pytanie, ale spodobało mi się wyzwanie w nim zawarte i wykorzystałem przewagę nowych rozwiązań dostępnych dla tego typu zadań.
Jan Vlcinsky

Odpowiedzi:

113
if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')
phihag
źródło
1
To chyba jedyny sposób, jeśli argparsenie ma wbudowanej opcji.
Adam Matan,
32
args = vars(parser.parse_args())
if not any(args.values()):
    parser.error('No arguments provided.')
brentlance
źródło
3
+1 za uogólnione rozwiązanie. Podobnie jak użycie vars(), które jest również przydatne do przekazywania starannie nazwanych opcji do konstruktora z **.
Lenna
Dokładnie to z tym robię. Dzięki!
brentlance
1
Cholera, podoba mi się to vars. Właśnie to zrobiłem .__dict__i wcześniej czułem się głupio.
Theo Belaire
1
świetne odpowiedzi. Zarówno "vars", jak i "any" były dla mnie nowe :-)
Vivek Jha
21

Jeśli nie część `` lub obie '' (początkowo to przegapiłem), możesz użyć czegoś takiego:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
    parser.error("One of --process or --upload must be given")

Chociaż prawdopodobnie lepszym pomysłem byłoby użycie podkomend .

Jacek Konieczny
źródło
4
Myślę, że chce zezwolić na --processOR --upload, a nie XOR. Zapobiega to równoczesnemu ustawianiu obu opcji.
phihag
+1, ponieważ wspomniałeś o podkomendach. A jednak - jak ktoś wskazał w komentarzach -xi --xxxsą to parametry typowo opcjonalne.
mac,
20

Wiem, że to stare jak brud, ale sposób na wymaganie jednej opcji, ale zakazanie więcej niż jednego (XOR), jest taki:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()
print args

Wynik:

>opt.py  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: one of the arguments -process -upload is required  

>opt.py -upload  
Namespace(process=False, upload=True)  

>opt.py -process  
Namespace(process=True, upload=False)  

>opt.py -upload -process  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: argument -process: not allowed with argument -upload  
Knut
źródło
4
Niestety OP nie chce XOR. Jest albo jedno, albo oba, ale nie żadne, więc twój ostatni przypadek testowy nie spełnia ich wymagań.
kdopen
3
@kdopen: respondent wyjaśnił, że jest to odmiana pierwotnego pytania, które uznałem za przydatne: „sposób na wymaganie jednej opcji, ale zakazanie więcej niż jednej” Być może etykieta wymiany stosów wymagałaby zamiast tego nowego pytania . Ale obecność tej odpowiedzi pomogła mi ...
erik.weathers
2
Ten post nie odpowiada na początkowe pytanie
Marc
2
Jak to odpowiada na pytanie „przynajmniej jeden”?
xaxxon
2
Niestety OP nie chce XOR.
duckman_1991
8

Przegląd wymagań

  • użyj argparse(zignoruję ten)
  • zezwalaj na wywołanie jednej lub dwóch akcji (przynajmniej jedna wymagana).
  • spróbuj przez Pythonic (wolałbym to nazwać jak "POSIX")

Istnieją również pewne domniemane wymagania dotyczące życia w wierszu poleceń:

  • wyjaśnić użytkownikowi użycie w łatwy do zrozumienia sposób
  • opcje są opcjonalne
  • pozwalają na określenie flag i opcji
  • pozwalają na łączenie z innymi parametrami (jak nazwa pliku lub nazwy).

Przykładowe rozwiązanie przy użyciu docopt(plik managelog.py):

"""Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Spróbuj go uruchomić:

$ python managelog.py
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Pokaż pomoc:

$ python managelog.py -h
Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>

I użyj go:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
 '--pswd': 'secret',
 '--user': 'user',
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': False,
 'upload': True}

Krótka alternatywa short.py

Może być jeszcze krótszy wariant:

"""Manage logfiles
Usage:
    short.py [options] (process|upload)... -- <logfile>...
    short.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Sposób użycia wygląda następująco:

$ python short.py -V process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 1,
 'upload': 1}

Zauważ, że zamiast wartości logicznych dla kluczy „proces” i „przesyłanie” są liczniki.

Okazuje się, że nie możemy zapobiec powielaniu tych słów:

$ python short.py -V process process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 2,
 'upload': 1}

Wnioski

Zaprojektowanie dobrego interfejsu wiersza poleceń może czasami być trudne.

Program oparty na wierszu poleceń ma wiele aspektów:

  • dobry projekt linii poleceń
  • wybranie / użycie odpowiedniego parsera

argparse oferuje dużo, ale ogranicza możliwe scenariusze i może stać się bardzo skomplikowany.

Dzięki temu docoptrzeczy idą znacznie krócej, zachowując czytelność i oferując wysoki stopień elastyczności. Jeśli uda Ci się uzyskać przeanalizowane argumenty ze słownika i wykonać niektóre konwersje (na liczby całkowite, otwieranie plików ..) ręcznie (lub przez inną bibliotekę o nazwie schema), może się okazać, że docoptdobrze nadaje się do analizy z wiersza poleceń.

Jan Vlcinsky
źródło
Nigdy nie słyszałem o docopt, świetna sugestia!
Ton van den Heuvel
@TonvandenHeuvel Good. Chcę tylko potwierdzić, nadal używam go jako mojego preferowanego rozwiązania dla interfejsów wiersza poleceń.
Jan Vlcinsky
Najlepsza odpowiedź evar, dziękuję za szczegółowe przykłady.
jnovack
5

Jeśli chcesz, aby program w Pythonie działał z co najmniej jednym parametrem, dodaj argument, który nie ma przedrostka opcji (domyślnie - lub -) i ustaw nargs=+(wymagany jest co najmniej jeden argument). Problem z tą metodą, który znalazłem polega na tym, że jeśli nie podasz argumentu, argparse wygeneruje błąd „za mało argumentów” i nie wyświetli menu pomocy. Jeśli nie potrzebujesz tej funkcji, oto jak to zrobić w kodzie:

import argparse

parser = argparse.ArgumentParser(description='Your program description')
parser.add_argument('command', nargs="+", help='describe what a command is')
args = parser.parse_args()

Myślę , że kiedy dodajesz argument z prefiksami opcji, nargs zarządza całym parserem argumentów, a nie tylko opcją. (Chodzi mi o to, że jeśli masz --optionflagę z nargs="+", to --optionflaga oczekuje co najmniej jednego argumentu. Jeśli masz optionz nargs="+", oczekuje co najmniej jednego argumentu w sumie).

NuclearPeon
źródło
Mógłbyś dodać choices=['process','upload']do tego argumentu.
hpaulj
5

W przypadku http://bugs.python.org/issue11588 badam sposoby uogólnienia mutually_exclusive_groupkoncepcji obsługi takich przypadków.

Z tego rozwoju argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py jestem w stanie napisać:

parser = argparse.ArgumentParser(prog='PROG', 
    description='Log archiver arguments.')
group = parser.add_usage_group(kind='any', required=True,
    title='possible actions (at least one is required)')
group.add_argument('-p', '--process', action='store_true')
group.add_argument('-u', '--upload',  action='store_true')
args = parser.parse_args()
print(args)

który daje help:

usage: PROG [-h] (-p | -u)

Log archiver arguments.

optional arguments:
  -h, --help     show this help message and exit

possible actions (at least one is required):
  -p, --process
  -u, --upload

Akceptuje dane wejściowe, takie jak „-u”, „-up”, „--proc --up” itp.

Kończy się uruchomieniem testu podobnego do https://stackoverflow.com/a/6723066/901925 , chociaż komunikat o błędzie musi być wyraźniejszy:

usage: PROG [-h] (-p | -u)
PROG: error: some of the arguments process upload is required

Zastanawiam się:

  • czy parametry są kind='any', required=Truewystarczająco jasne (zaakceptuj dowolną grupę; wymagany jest przynajmniej jeden)?

  • czy użycie jest (-p | -u)jasne? Wymagana grupa mutually_exclusive_group daje to samo. Czy istnieje inna notacja?

  • czy używanie takiej grupy jest bardziej intuicyjne niż phihag'sprosty test?

hpaulj
źródło
Nie mogę znaleźć żadnej wzmianki o add_usage_groupna tej stronie: docs.python.org/2/library/argparse.html ; czy możesz podać link do dokumentacji?
P. Myer Nore
@ P.MyerNore, podałem link - na początku tej odpowiedzi. To nie zostało wprowadzone do produkcji.
hpaulj
5

Najlepszym sposobem na to jest użycie wbudowanego modułu Pythona add_mutually_exclusive_group .

parser = argparse.ArgumentParser(description='Log archiver arguments.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Jeśli chcesz, aby tylko jeden argument był wybierany w wierszu poleceń, po prostu użyj required = True jako argument dla grupy

group = parser.add_mutually_exclusive_group(required=True)
faizan baig
źródło
2
Jak to daje ci „przynajmniej jednego” - czy nie oznacza to „dokładnie jednego”?
xaxxon
3
Niestety OP nie chce XOR. OP szuka OR
duckman_1991
To nie było odpowiedzią na pytanie OP, ale odpowiedziała na moje, więc i tak dziękuję ¯_ (ツ) _ / ¯
rosstex
2

Może użyć sub-parserów?

import argparse

parser = argparse.ArgumentParser(description='Log archiver arguments.')
subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
parser_process = subparsers.add_parser('process', help='Process logs')
parser_upload = subparsers.add_parser('upload', help='Upload logs')
args = parser.parse_args()

print("Subparser: ", args.subparser_name)

Teraz --helppokazuje:

$ python /tmp/aaa.py --help
usage: aaa.py [-h] {process,upload} ...

Log archiver arguments.

positional arguments:
  {process,upload}  sub-command help
    process         Process logs
    upload          Upload logs

optional arguments:
  -h, --help        show this help message and exit
$ python /tmp/aaa.py
usage: aaa.py [-h] {process,upload} ...
aaa.py: error: too few arguments
$ python3 /tmp/aaa.py upload
Subparser:  upload

Możesz również dodać dodatkowe opcje do tych podparserów. Zamiast tego dest='subparser_name'możesz również przypisać funkcje do bezpośredniego wywołania z danego polecenia podrzędnego (zobacz dokumentację).

jhutar
źródło
2

Osiąga to cel i zostanie również odzwierciedlone w automatycznie generowanym --helpwyjściu argparse , co jest tym, czego chce większość rozsądnych programistów (działa również z opcjonalnymi argumentami):

parser.add_argument(
    'commands',
    nargs='+',                      # require at least 1
    choices=['process', 'upload'],  # restrict the choice
    help='commands to execute'
)

Oficjalne dokumenty na ten temat: https://docs.python.org/3/library/argparse.html#choices

Pion
źródło
1

Użyj append_const do listy działań, a następnie sprawdź, czy lista jest wypełniona:

parser.add_argument('-process', dest=actions, const="process", action='append_const')
parser.add_argument('-upload',  dest=actions, const="upload", action='append_const')

args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

Możesz nawet określić metody bezpośrednio w stałych.

def upload:
    ...

parser.add_argument('-upload',  dest=actions, const=upload, action='append_const')
args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

else:
    for action in args.actions:
        action()
burza_m2138
źródło