Рішення, яке надає @Vikas, не вдається для необов’язкових аргументів, специфічних для підкоманди, але підхід є дійсним. Ось покращена версія:
import argparse
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='foo help')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
parser_b = subparsers.add_parser('command_b', help='command_b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')
argv = ['--foo', 'command_a', '12', 'command_b', '--baz', 'Z']
while argv:
print(argv)
options, argv = parser.parse_known_args(argv)
print(options)
if not options.subparser_name:
break
Це використовує parse_known_args
замість parse_args
. parse_args
переривається, як тільки зустрічається аргумент, невідомий поточному підпарсеру,parse_known_args
повертає їх як друге значення у повернутому кортежі. При такому підході решта аргументів знову подається до синтаксичного аналізатора. Тож для кожної команди створюється новий простір імен.
Зверніть увагу, що в цьому базовому прикладі всі глобальні параметри додаються лише до першого простору імен, а не до наступних просторів імен.
Цей підхід чудово працює у більшості ситуацій, але має три важливі обмеження:
- Неможливо використовувати один і той же необов'язковий аргумент для різних підкоманд, наприклад
myprog.py command_a --foo=bar command_b --foo=bar
.
- Неможливо використовувати позиційні аргументи змінної довжини з підкомандами (
nargs='?'
або nargs='+'
або nargs='*'
).
- Будь-який відомий аргумент аналізується без `` злому '' за новою командою. Наприклад,
PROG --foo command_b command_a --baz Z 12
з наведеним вище кодом, --baz Z
буде споживатися command_b
, а не command_a
.
Ці обмеження є прямим обмеженням аргпарсу. Ось простий приклад, який показує обмеження argparse - навіть при використанні однієї підкоманди -:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('spam', nargs='?')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
parser_b = subparsers.add_parser('command_b', help='command_b help')
options = parser.parse_args('command_a 42'.split())
print(options)
Це підніме error: argument subparser_name: invalid choice: '42' (choose from 'command_a', 'command_b')
.
Причиною є те, що внутрішній метод argparse.ArgParser._parse_known_args()
занадто жадібний і припускає, що command_a
це значення необов’язкового spam
аргументу. Зокрема, під час «розбиття» необов’язкових та позиційних аргументів _parse_known_args()
не розглядаються назви груп (як command_a
або command_b
), а лише те, де вони трапляються у списку аргументів. Він також передбачає, що будь-яка підкоманда буде використовувати всі інші аргументи. Це обмеження argparse
також заважає належній реалізації багатокомандних підпарсерів. На жаль, це означає, що для належної реалізації потрібен повний перепис argparse.ArgParser._parse_known_args()
методу, що складає понад 200 рядків коду.
Враховуючи ці обмеження, можливо, просто повернутися до одного аргументу з множинним вибором замість підкоманд:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--bar', type=int, help='bar help')
parser.add_argument('commands', nargs='*', metavar='COMMAND',
choices=['command_a', 'command_b'])
options = parser.parse_args('--bar 2 command_a command_b'.split())
print(options)
Можна навіть перерахувати різні команди в інформації про використання, див. Мою відповідь https://stackoverflow.com/a/49999185/428542