Wyświetla komunikat pomocy z python argparse, gdy skrypt jest wywoływany bez żadnych argumentów

226

To może być proste. Załóżmy, że mam program, który używa argparse do przetwarzania argumentów / opcji wiersza poleceń. Poniższe polecenie wydrukuje komunikat „pomocy”:

./myprogram -h

lub:

./myprogram --help

Ale jeśli uruchomię skrypt bez żadnych argumentów, nic nie da. Chcę, aby wyświetlało komunikat o użyciu, gdy jest wywoływany bez argumentów. Jak to się robi?

musashiXXX
źródło

Odpowiedzi:

273

Ta odpowiedź pochodzi od Stevena Betharda w grupach Google . Przekazuję go tutaj, aby ułatwić dostęp osobom nieposiadającym konta Google.

Możesz zastąpić domyślne zachowanie errormetody:

import argparse
import sys

class MyParser(argparse.ArgumentParser):
    def error(self, message):
        sys.stderr.write('error: %s\n' % message)
        self.print_help()
        sys.exit(2)

parser = MyParser()
parser.add_argument('foo', nargs='+')
args = parser.parse_args()

Zauważ, że powyższe rozwiązanie wydrukuje komunikat pomocy za każdym razem, gdy error zostanie wyzwolona metoda. Na przykład test.py --blahwydrukuje komunikat pomocy, jeśli --blahnie jest prawidłową opcją.

Jeśli chcesz wydrukować komunikat pomocy tylko wtedy, gdy w wierszu poleceń nie podano argumentów, być może jest to najłatwiejszy sposób:

import argparse
import sys

parser=argparse.ArgumentParser()
parser.add_argument('foo', nargs='+')
if len(sys.argv)==1:
    parser.print_help(sys.stderr)
    sys.exit(1)
args=parser.parse_args()

Zauważ, że parser.print_help()domyślnie drukuje na standardowym wyjściu. Jak sugeruje init_js , użyj parser.print_help(sys.stderr)do drukowania na stderr.

unutbu
źródło
Tak ... zastanawiałem się nad tym, czy argparse ma sposób poradzić sobie z tym scenariuszem. Dzięki!
musashiXXX
6
W drugim rozwiązaniu używam parser.print_usage()zamiast parser.print_help()- komunikat pomocy zawiera użycie, ale jest bardziej szczegółowe.
user2314737,
5
Głosowałbym za drugą częścią odpowiedzi, ale zastąpienie error()wydaje mi się okropnym pomysłem. Służy innym celom, nie jest przeznaczony do drukowania przyjaznego użytkowania lub pomocy.
Peterino
@Peterino - przesłonięcie występuje w klasie potomnej, więc nie powinno to stanowić problemu. To jest wyraźne.
Marcel Wilson
1
@unutbu WONDERFUL! Dokładnie to, czego potrzebowałem. Jedno pytanie, czy można to zastosować również do podkomend? Zwykle dostaję `` Namespace (output = None) `. Jak mogę łatwo wywołać błąd we WSZYSTKICH podkomendach? Chciałbym wywołać tam błąd.
Jonathan Komar,
56

Zamiast pisać klasę można zamiast tego użyć try / wyjątek

try:
    options = parser.parse_args()
except:
    parser.print_help()
    sys.exit(0)

Zaletą jest to, że przepływ pracy jest bardziej przejrzysty i nie potrzebujesz klasy kodu pośredniczącego. Minusem jest to, że pierwsza linia „użytkowania” jest drukowana dwukrotnie.

Będzie to wymagało co najmniej jednego obowiązkowego argumentu. Bez obowiązkowych argumentów podanie zerowych argumentów w wierszu poleceń jest poprawne.

Vacri
źródło
ja też wolę to od przyjętej odpowiedzi. Dodanie klasy jest nadmiernie obciążone dla drukowania pomocy, gdy argumenty są nieoczekiwane. Pozwól, aby doskonały moduł argparse obsługiwał dla Ciebie przypadki błędów.
Nicole Finnie,
7
Ten kod wypisuje pomoc 2 razy, jeśli -hflaga jest używana, a niepotrzebne wypisuje pomoc, jeśli --versionflaga jest używana. Aby złagodzić te problemy, możesz sprawdzić typ błędu w następujący sposób:except SystemExit as err: if err.code == 2: parser.print_help()
pkowalczyk
25

Za pomocą argparse możesz zrobić:

parser.argparse.ArgumentParser()
#parser.add_args here

#sys.argv includes a list of elements starting with the program
if len(sys.argv) < 2:
    parser.print_usage()
    sys.exit(1)
cgseller
źródło
5
Musi to nastąpić przed telefonem doparser.parse_args()
Boba Stein
18

Jeśli masz argumenty, które należy podać, aby skrypt mógł zostać uruchomiony - użyj wymaganego parametru ArgumentParser, jak pokazano poniżej: -

parser.add_argument('--foo', required=True)

parse_args () zgłosi błąd, jeśli skrypt zostanie uruchomiony bez żadnych argumentów.

pd321
źródło
2
Jest to najprostsze rozwiązanie i będzie działać również z określonymi nieprawidłowymi opcjami.
Steve Scherer
1
Zgoda. Myślę, że zawsze lepiej jest wykorzystać wbudowane możliwości parsera argumentów, niż napisać dodatkowy moduł obsługi.
Christopher Hunter
18

Jeśli skojarzysz funkcje domyślne dla parserów (pod), jak wspomniano poniżej add_subparsers, możesz po prostu dodać je jako akcję domyślną:

parser = argparse.ArgumentParser()
parser.set_defaults(func=lambda x: parser.print_usage())
args = parser.parse_args()
args.func(args)

Dodaj try-wyjątkiem, jeśli zgłaszane są wyjątki z powodu braku argumentów pozycyjnych.

AManOfScience
źródło
1
Ta odpowiedź jest tak niedoceniana. Prosty i działa bardzo dobrze z podparserami.
orodbhen
Świetna odpowiedź! Jedyną zmianą, jaką wprowadziłem, było użycie lambda bez parametru.
boh717,
12

Najczystszym rozwiązaniem będzie ręczne przekazanie domyślnego argumentu, jeśli nie podano żadnego w wierszu poleceń:

parser.parse_args(args=None if sys.argv[1:] else ['--help'])

Kompletny przykład:

import argparse, sys

parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost', help='Host to connect to')
# parse arguments
args = parser.parse_args(args=None if sys.argv[1:] else ['--help'])

# use your args
print("connecting to {}".format(args.host))

Spowoduje to wydrukowanie pełnej pomocy (nie krótkiego użycia), jeśli zostanie wywołany bez argumentów.

Ievgen Popovych
źródło
2
sys.argv[1:]jest bardzo powszechnym idiomem. Widzę parser.parse_args(None if sys.argv[1:] else ['-h'])więcej idiomatycznych i czystszych.
Nuno André
1
@ Dzięki NunoAndré - zaktualizowałem odpowiedź. Rzeczywiście wydaje się bardziej pythonowy.
Ievgen Popovych
10

Wrzucam tutaj moją wersję na stos:

import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
if not vars(args):
    parser.print_help()
    parser.exit(1)

Możesz zauważyć parser.exit- robię to głównie dlatego, że zapisuje wiersz importu, jeśli był to jedyny powód sysw pliku ...

pauricthelodger
źródło
parser.exit (1) jest fajny! Dobry dodatek
cgseller
4
Niestety parser.parse_args () zakończy działanie, jeśli brakuje argumentu pozycyjnego. To działa tylko przy użyciu opcjonalnych argumentów.
Marcel Wilson
1
@MarcelWilson, rzeczywiście - dobry połów! Zastanowię się, jak to zmienić.
pauricthelodger
not vars(args)może nie działać, gdy argumenty mają defaultmetodę.
funkid
5

Istnieje para jednowierszowych znaków z sys.argv[1:](bardzo powszechnym idiomem Pythona, który odnosi się do argumentów wiersza poleceń, jestsys.argv[0] nazwą skryptu), które mogą wykonać to zadanie.

Pierwszy z nich jest zrozumiały, czysty i pytoniczny:

args = parser.parse_args(None if sys.argv[1:] else ['-h'])

Drugi jest trochę trudniejszy. Łącząc wcześniej oceniany fakt, że pusta lista jest Falsez odpowiednikami True == 1i False == 0otrzymujesz to:

args = parser.parse_args([None, ['-h']][not sys.argv[1:]])

Może za dużo nawiasów, ale całkiem jasne, czy dokonano wcześniejszego wyboru argumentu.

_, *av = sys.argv
args = parser.parse_args([None, ['-h']][not av])
Nuno André
źródło
1
parser.print_help()
parser.exit()

parser.exitSposób akceptować również status(returnCode) orazmessage wartość (m.in. tylnego przełamane siebie!).

uparty przykład :)

#!/usr/bin/env python3

""" Example argparser based python file
"""

import argparse

ARGP = argparse.ArgumentParser(
    description=__doc__,
    formatter_class=argparse.RawTextHelpFormatter,
)
ARGP.add_argument('--example', action='store_true', help='Example Argument')


def main(argp=None):
    if argp is None:
        argp = ARGP.parse_args()  # pragma: no cover

    if 'soemthing_went_wrong' and not argp.example:
        ARGP.print_help()
        ARGP.exit(status=128, message="\nI just don't know what went wrong, maybe missing --example condition?\n")


if __name__ == '__main__':
    main()  # pragma: no cover

Przykładowe wywołania:

$ python3 ~ / helloworld.py; echo $?
użycie: helloworld.py [-h] [--example]

 Przykładowy plik python oparty na argparserze

opcjonalne argumenty:
  -h, --help pokaż ten komunikat pomocy i wyjdź
  - przykład Przykład Argument

Po prostu nie wiem, co poszło nie tak, może brakowało - przykładowy stan?
128
$ python3 ~ / helloworld.py --example; echo $?
0
ThorSummoner
źródło
0

Ustaw argumenty pozycyjne za pomocą nargs i sprawdź, czy argumenty pozycyjne są puste.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file', nargs='?')
args = parser.parse_args()
if not args.file:
    parser.print_help()

Odwołanie Nargs Python

zerocog
źródło
0

Oto inny sposób, aby to zrobić, jeśli potrzebujesz czegoś elastycznego, w którym chcesz wyświetlić pomoc, jeśli określone parametry zostaną przekazane, żaden lub więcej niż jeden sprzeczny argument:

import argparse
import sys

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--days', required=False,  help="Check mapped inventory that is x days old", default=None)
    parser.add_argument('-e', '--event', required=False, action="store", dest="event_id",
                        help="Check mapped inventory for a specific event", default=None)
    parser.add_argument('-b', '--broker', required=False, action="store", dest="broker_id",
                        help="Check mapped inventory for a broker", default=None)
    parser.add_argument('-k', '--keyword', required=False, action="store", dest="event_keyword",
                        help="Check mapped inventory for a specific event keyword", default=None)
    parser.add_argument('-p', '--product', required=False, action="store", dest="product_id",
                        help="Check mapped inventory for a specific product", default=None)
    parser.add_argument('-m', '--metadata', required=False, action="store", dest="metadata",
                        help="Check mapped inventory for specific metadata, good for debugging past tix", default=None)
    parser.add_argument('-u', '--update', required=False, action="store_true", dest="make_updates",
                        help="Update the event for a product if there is a difference, default No", default=False)
    args = parser.parse_args()

    days = args.days
    event_id = args.event_id
    broker_id = args.broker_id
    event_keyword = args.event_keyword
    product_id = args.product_id
    metadata = args.metadata
    make_updates = args.make_updates

    no_change_counter = 0
    change_counter = 0

    req_arg = bool(days) + bool(event_id) + bool(broker_id) + bool(product_id) + bool(event_keyword) + bool(metadata)
    if not req_arg:
        print("Need to specify days, broker id, event id, event keyword or past tickets full metadata")
        parser.print_help()
        sys.exit()
    elif req_arg != 1:
        print("More than one option specified. Need to specify only one required option")
        parser.print_help()
        sys.exit()

    # Processing logic here ...

Twoje zdrowie!

radtek
źródło
Myślę, że znacznie łatwiej byłoby korzystać z akapitów lub grupy wykluczającej się wzajemnie
Tim Bray,
0

Jeśli twoje polecenie jest czymś, w którym użytkownik musi wybrać jakąś akcję, użyj grupy wzajemnie się wykluczającej z wymaganym = Prawda .

Jest to rodzaj rozszerzenia odpowiedzi udzielonej przez pd321.

import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--batch", action='store', type=int,  metavar='pay_id')
group.add_argument("--list", action='store_true')
group.add_argument("--all", action='store_true', help='check all payments')

args=parser.parse_args()

if args.batch:
    print('batch {}'.format(args.batch))

if args.list:
    print('list')

if args.all:
    print('all')

Wynik:

$ python3
użycie a_test.py : a_test.py [-h] (--batch pay_id | --list | --all)
a_test.py: błąd: jeden z argumentów --batch --list --all jest wymagany

To tylko podstawowa pomoc. A niektóre inne odpowiedzi dadzą ci pełną pomoc. Ale przynajmniej użytkownicy wiedzą, że mogą zrobić -h

Tim Bray
źródło
0

Nie jest to dobre (ponieważ przechwytuje wszystkie błędy), ale:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)

    parser.add_argument(...)
    ...

Oto definicja errorfunkcji ArgumentParserklasy:

https://github.com/python/cpython/blob/276eb67c29d05a93fbc22eea5470282e73700d20/Lib/argparse.py#L2374

. Jak widać, po podpisie potrzeba dwóch argumentów. Jednak funkcje poza klasą nic nie wiedzą o pierwszym argumencie: selfponieważ, z grubsza mówiąc, jest to parametr dla klasy. (Wiem, że wiesz ...) W ten sposób, po prostu przekazać własne selfi messagena _error(...)nie może (

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error
    ...
...

wyświetli:

...
"AttributeError: 'str' object has no attribute 'print_help'"

). Można przekazać parser( self) w _errorfunkcji, nazywając go:

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)
    ...
...

, ale nie chcesz teraz wychodzić z programu. Następnie zwróć:

def _error(parser):
    def wrapper():
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. Niemniej jednak parsernie wie, że został zmodyfikowany, więc gdy wystąpi błąd, wyśle ​​przyczynę (przy okazji, jego zlokalizowane tłumaczenie). Więc przechwyć to:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. Teraz, gdy wystąpi błąd i parserwyśle ​​przyczynę, przechwycisz go, popatrzysz na to i ... wyrzucisz.

Maxim Temny
źródło