Argparse: Wymagane argumenty wymienione w „Argumentach opcjonalnych”?

229

Używam następującego prostego kodu, aby przeanalizować niektóre argumenty; zwróć uwagę, że jeden z nich jest wymagany. Niestety, gdy użytkownik uruchamia skrypt bez podania argumentu, wyświetlany tekst użycia / pomocy nie wskazuje, że istnieje opcjonalny argument, co uważam za bardzo mylące. Jak mogę uzyskać od Pythona wskazania, że ​​argument nie jest opcjonalny?

Oto kod:

import argparse
if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Foo')
    parser.add_argument('-i','--input', help='Input file name', required=True)
    parser.add_argument('-o','--output', help='Output file name', default="stdout")
    args = parser.parse_args()
    print ("Input file: %s" % args.input )
    print ("Output file: %s" % args.output )

Podczas uruchamiania powyżej kodu bez podania wymaganego argumentu otrzymuję następujące dane wyjściowe:

usage: foo.py [-h] -i INPUT [-o OUTPUT]

Foo

optional arguments:
    -h, --help            show this help message and exit
    -i INPUT, --input INPUT
                          Input file name
    -o OUTPUT, --output OUTPUT
                          Output file name
mort
źródło
5
W linii użytkowania -i INPUTczęść nie jest otoczona nawiasami kwadratowymi, co subtelność wskazuje, że jest to rzeczywiście wymagane. Możesz także ręcznie wyjaśnić to przez helpparam
Jaime RGP
7
@JaimeRGP Tak, ale to oczywiście nie wystarczy, a także jest mniej niż widoczne. Przypisana nazwa grupy optional argumentsdla wymaganych argumentów nadal wprowadza w błąd.
Acumenus,

Odpowiedzi:

316

Parametry rozpoczynające się od -lub --są zwykle uważane za opcjonalne. Wszystkie pozostałe parametry są parametrami pozycyjnymi i jako takie są wymagane przez projekt (podobnie jak argumenty funkcji pozycyjnych). Można wymagać opcjonalnych argumentów, ale jest to nieco sprzeczne z ich projektem. Ponieważ nadal są częścią argumentów niepozycjonujących, nadal będą wymienione w mylącym nagłówku „argumenty opcjonalne”, nawet jeśli są wymagane. Brakujące nawiasy kwadratowe w części dotyczącej użytkowania wskazują jednak, że rzeczywiście są one wymagane.

Zobacz także dokumentację :

Ogólnie moduł argparse zakłada, że ​​flagi takie jak -f i --bar wskazują opcjonalne argumenty, które zawsze można pominąć w wierszu poleceń.

Uwaga: Wymagane opcje są ogólnie uważane za złą formę, ponieważ użytkownicy oczekują, że będą opcjonalne, dlatego należy ich unikać, gdy jest to możliwe.

To powiedziawszy, nagłówki „argumenty pozycyjne” i „argumenty opcjonalne” w pomocy są generowane przez dwie grupy argumentów, na które argumenty są automatycznie dzielone. Teraz możesz „włamać się” i zmienić nazwę opcjonalnych, ale o wiele bardziej eleganckim rozwiązaniem byłoby utworzenie innej grupy dla „wymaganych nazwanych argumentów” (lub jakkolwiek chcesz je nazwać):

parser = argparse.ArgumentParser(description='Foo')
parser.add_argument('-o', '--output', help='Output file name', default='stdout')
requiredNamed = parser.add_argument_group('required named arguments')
requiredNamed.add_argument('-i', '--input', help='Input file name', required=True)
parser.parse_args(['-h'])
usage: [-h] [-o OUTPUT] -i INPUT

Foo

optional arguments:
  -h, --help            show this help message and exit
  -o OUTPUT, --output OUTPUT
                        Output file name

required named arguments:
  -i INPUT, --input INPUT
                        Input file name
szturchać
źródło
Mam ten sam problem. Próbowałem ci rozwiązania. Dodaje argumenty do nowej grupy, ale mój kod wydaje się później nie działać. Wszelkie rozwiązania byłyby mile widziane. Link do mojego kodu - pastebin.com/PvC2aujz
Zarar Mahmud
1
@ZararMahmud: Podajesz puste argumenty w wierszu 24 kodu: parser.parse_args([])Zamiast tego używaj parser.parse_args()bez argumentów, aby przechwycić zawartość sys.argv. Per argparse
Devin
@poke: Ładne rozwiązanie! Ale to nie pomaga w przypadku, gdy potrzebujesz wzajemnie wykluczających się grup, czy czegoś mi brakuje?
Sędzia
@ Judge polecam przeczytać ten pymotw.com/3/argparse/#mutually-exclusive-options
Peter Moore
79

Ponieważ wolę podać wymagane argumenty przed opcjonalnym, włamuję się do nich za pomocą:

    parser = argparse.ArgumentParser()
    parser._action_groups.pop()
    required = parser.add_argument_group('required arguments')
    optional = parser.add_argument_group('optional arguments')
    required.add_argument('--required_arg', required=True)
    optional.add_argument('--optional_arg')
    return parser.parse_args()

i to daje:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
               [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  --optional_arg OPTIONAL_ARG

Mogę żyć bez pomocy w grupie argumentów opcjonalnych.

Karl Rosaen
źródło
3
Czy to faktycznie zmusza argparse do traktowania któregokolwiek z argumentów zgodnie z wymaganiami?
Anthony
6
Myślę, że „wymagany” argument nadal musi zostać ustawiony podczas dodawania argumentu.
Karl Rosaen
To naprawdę miłe.
Paul Cezanne,
7
@Anthony - nie, do tego potrzebny jest parametr „wymagany = prawda” w narzędziu add_argument. Powyższa odpowiedź ilustruje tylko grupowanie argumentów.
user2275693
47

Kompilacja @Karl Rosaen

parser = argparse.ArgumentParser()
optional = parser._action_groups.pop() # Edited this line
required = parser.add_argument_group('required arguments')
# remove this line: optional = parser...
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')
parser._action_groups.append(optional) # added this line
return parser.parse_args()

i to daje:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
RalphyZ
źródło
1
BTW, czy są jakieś sposoby (metody), jak uzyskać dostęp _action_groupbez dostępu do chronionego członka? W moim przypadku muszę dodać argument do już istniejącej (niestandardowej) grupy.
machin
To jest świetne. Rozwiązuje element --help wyświetlany na drugiej opcjonalnej liście.
Jeremy,
Uwaga : ta odpowiedź psuje ujawniony interfejs API, sprawdź odpowiedź Bryan_D poniżej.
lol
18

Jeszcze raz, budując @RalphyZ

Ten nie psuje ujawnionego API.

from argparse import ArgumentParser, SUPPRESS
# Disable default help
parser = ArgumentParser(add_help=False)
required = parser.add_argument_group('required arguments')
optional = parser.add_argument_group('optional arguments')

# Add back help 
optional.add_argument(
    '-h',
    '--help',
    action='help',
    default=SUPPRESS,
    help='show this help message and exit'
)
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')

Który pokaże to samo co powyżej i powinien przetrwać przyszłe wersje:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
Bryan_D
źródło
Czy możesz wyjaśnić, w jaki sposób odpowiedź RalphyZ psuje ujawniony interfejs API?
jeremysprofile,
5
_action_groupsjest przeznaczony wyłącznie do użytku wewnętrznego. Dlatego nie ma gwarancji zgodności między wersjami.
Bryan_D,