Używam argparse
do programu Pythona, który może -process
, -upload
lub 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ć argparse
wymuszenie wyboru co najmniej jednego parametru?
AKTUALIZACJA:
Po komentarzach: W jaki sposób Pythonic parametryzuje program z co najmniej jedną opcją?
-x
jest powszechnie flagą i opcjonalną. Wytnij,-
jeśli jest to wymagane.process
ustawić zachowania domyślnego (bez potrzeby określania jakichkolwiek opcji) i pozwolić użytkownikowi zmienić to na,upload
jeśli ta opcja jest ustawiona? Zwykle opcje powinny być opcjonalne, stąd nazwa. Należy unikać wymaganych opcji (jest to również wargparse
dokumentacji).Odpowiedzi:
if not (args.process or args.upload): parser.error('No action requested, add -process or -upload')
źródło
argparse
nie ma wbudowanej opcji.args = vars(parser.parse_args()) if not any(args.values()): parser.error('No arguments provided.')
źródło
vars()
, które jest również przydatne do przekazywania starannie nazwanych opcji do konstruktora z **.vars
. Właśnie to zrobiłem.__dict__
i wcześniej czułem się głupio.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 .
źródło
--process
OR--upload
, a nie XOR. Zapobiega to równoczesnemu ustawianiu obu opcji.-x
i--xxx
są to parametry typowo opcjonalne.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
źródło
Przegląd wymagań
argparse
(zignoruję ten)Istnieją również pewne domniemane wymagania dotyczące życia w wierszu poleceń:
Przykładowe rozwiązanie przy użyciu
docopt
(plikmanagelog.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ć:
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:
argparse
oferuje dużo, ale ogranicza możliwe scenariusze i może stać się bardzo skomplikowany.Dzięki temu
docopt
rzeczy 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 nazwieschema
), może się okazać, żedocopt
dobrze nadaje się do analizy z wiersza poleceń.źródło
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
--option
flagę znargs="+"
, to--option
flaga oczekuje co najmniej jednego argumentu. Jeśli maszoption
znargs="+"
, oczekuje co najmniej jednego argumentu w sumie).źródło
choices=['process','upload']
do tego argumentu.W przypadku http://bugs.python.org/issue11588 badam sposoby uogólnienia
mutually_exclusive_group
koncepcji 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=True
wystarczają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's
prosty test?źródło
add_usage_group
na tej stronie: docs.python.org/2/library/argparse.html ; czy możesz podać link do dokumentacji?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)
źródło
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
--help
pokazuje:$ 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ę).źródło
Osiąga to cel i zostanie również odzwierciedlone w automatycznie generowanym
--help
wyjś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
źródło
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()
źródło