Необязательный подпарсер argparse (для --version)
У меня есть следующий код (используя Python 2.7):
# shared command line options, like --version or --verbose
parser_shared = argparse.ArgumentParser(add_help=False)
parser_shared.add_argument('--version', action='store_true')
# the main parser, inherits from `parser_shared`
parser = argparse.ArgumentParser(description='main', parents=[parser_shared])
# several subcommands, which can't inherit from the main parser, since
# it would expect subcommands ad infinitum
subparsers = parser.add_subparsers('db', parents=[parser_shared])
...
args = parser.parse_args()
Теперь я хотел бы иметь возможность вызвать эту программу, например, с --version
добавляется к обычной программе или некоторой подкоманде:
$ prog --version
0.1
$ prog db --version
0.1
По сути, мне нужно объявить необязательные подпарсеры. Я знаю, что это на самом деле не поддерживается, но есть ли обходные пути или альтернативы?
Редактировать: сообщение об ошибке, которое я получаю:
$ prog db --version
# works fine
$ prog --version
usage: ....
prog: error: too few arguments
7 ответов
Согласно документации, --version
с action='version'
(а не с action='store_true'
) автоматически печатает номер версии:
parser.add_argument('--version', action='version', version='%(prog)s 2.0')
FWIW, я столкнулся с этим также, и закончил тем, что "решил" это, не используя подпарсеры (у меня уже была своя собственная система для помощи печати, таким образом, ничего не потерял там).
Вместо этого я делаю это:
parser.add_argument("command", nargs="?",
help="name of command to execute")
args, subcommand_args = parser.parse_known_args()
... а затем подкоманда создает свой собственный синтаксический анализатор (похожий на субпарсер), который работает только на subcommand_args
,
Это, кажется, реализует основную идею необязательного подпаратера. Мы анализируем стандартные аргументы, которые применяются ко всем подкомандам. Затем, если что-то осталось, мы вызываем парсер на остальном. Первичные аргументы являются родителями подкоманды, поэтому -h отображается правильно. Я планирую ввести интерактивное приглашение, если нет подкоманд.
import argparse
p1 = argparse.ArgumentParser( add_help = False )
p1.add_argument( ‘–flag1′ )
p2 = argparse.ArgumentParser( parents = [ p1 ] )
s = p2.add_subparsers()
p = s.add_parser( ‘group’ )
p.set_defaults( group=True )
( init_ns, remaining ) = p1.parse_known_args( )
if remaining:
p2.parse_args( args = remaining, namespace=init_ns )
else:
print( ‘Enter interactive loop’ )
print( init_ns )
Как обсуждалось в http://bugs.python.org/issue9253 (argparse: необязательные подпапсеры), начиная с Python 3.3 подпарсеры теперь являются необязательными. Это было непреднамеренным результатом изменения в том, как parse_args
проверено на наличие необходимых аргументов.
Я нашел выдумку, которая восстанавливает предыдущее (обязательные подпарасеры) поведение, явно устанавливая required
атрибут subparsers
действие.
parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True # the fudge
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()
Смотрите эту проблему для более подробной информации. Я ожидаю, что если и когда эта проблема будет исправлена должным образом, подпарсеры потребуются по умолчанию, с некоторой опцией, чтобы установить ее required
приписывать False
, Но есть большое отставание argparse
пластыри.
Пока мы ожидаем доставки этой функции, мы можем использовать такой код:
# Make sure that main is the default sub-parser
if '-h' not in sys.argv and '--help' not in sys.argv:
if len(sys.argv) < 2:
sys.argv.append('main')
if sys.argv[1] not in ('main', 'test'):
sys.argv = [sys.argv[0], 'main'] + sys.argv[1:]
Да, я только что проверил svn
, который используется в качестве примера объекта в add_subparsers()
документации, и он поддерживает только '--version' в основной команде:
python zacharyyoung$ svn log --version
Subcommand 'log' doesn't accept option '--version'
Type 'svn help log' for usage.
Еще:
# create common parser
parent_parser = argparse.ArgumentParser('parent', add_help=False)
parent_parser.add_argument('--version', action='version', version='%(prog)s 2.0')
# create the top-level parser
parser = argparse.ArgumentParser(parents=[parent_parser])
subparsers = parser.add_subparsers()
# create the parser for the "foo" command
parser_foo = subparsers.add_parser('foo', parents=[parent_parser])
Который дает:
python zacharyyoung$ ./arg-test.py --version
arg-test.py 2.0
python zacharyyoung$ ./arg-test.py foo --version
arg-test.py foo 2.0
Хотя ответ @eumiro адресован --version
опция, это может быть сделано только потому, что это особый случай для optparse. Разрешить общие вызовы:
prog
prog --verbose
prog --verbose main
prog --verbose db
и имеют prog --version
работать так же, как prog --verbose main
(а также prog main --verbose
) вы можете добавить метод в Argumentparser и вызвать его с именем подпаратера по умолчанию, непосредственно перед вызовом parse_args()
:
import argparse
import sys
def set_default_subparser(self, name, args=None):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
for arg in sys.argv[1:]:
if arg in ['-h', '--help']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if not subparser_found:
# insert default in first position, this implies no
# global options without a sub_parsers specified
if args is None:
sys.argv.insert(1, name)
else:
args.insert(0, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def do_main(args):
print 'main verbose', args.verbose
def do_db(args):
print 'db verbose:', args.verbose
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--version', action='version', version='%(prog)s 2.0')
subparsers = parser.add_subparsers()
sp = subparsers.add_parser('main')
sp.set_defaults(func=do_main)
sp.add_argument('--verbose', action='store_true')
sp = subparsers.add_parser('db')
sp.set_defaults(func=do_db)
parser.set_default_subparser('main')
args = parser.parse_args()
if hasattr(args, 'func'):
args.func(args)
set_default_subparser()
Метод является частью пакета https://bitbucket.org/ruamel/std.argparse.