Как иметь аргументы подпарасера ​​в отдельном пространстве имен с argparse?

У меня есть следующий тест-код

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", default = 0, type=int)

subparsers = parser.add_subparsers(dest = "parser_name")

parser_lan = subparsers.add_parser('car')
parser_lan.add_argument("--boo")
parser_lan.add_argument("--foo")

parser_serial = subparsers.add_parser('bus')
parser_serial.add_argument("--fun")

print parser.parse_args()

который определяет два подпарсера, имеющих различный набор аргументов. Когда я называю тест-код как

tester.py  --verbose 3 car --boo 1 --foo 2

Я получаю ожидаемый результат

Namespace(boo='1', foo='2', parser_name='car', verbose=3)

Вместо этого я хочу получить значения из каждого подпапера в отдельном пространстве имен или в dict, что-то вроде

Namespace(subparseargs={boo:'1', foo:'2'}, parser_name='car', verbose=3)

так что аргументы каждого подпаратера логически отделены от аргументов основного синтаксического анализатора (как verbose в этом примере).

Как я могу добиться этого, с аргументами для каждого подпаратера в том же пространстве имен (subparseargs в примере).

4 ответа

Решение

Я начал разрабатывать другой подход (но похожий на предложение Антона) и придумал гораздо более короткий код. Тем не менее, я не уверен, что мой подход является общим решением проблемы.

Подобно тому, что предлагает Anthon, я определяю новый метод, который создает список аргументов "верхнего уровня", которые хранятся в argsв то время как все остальные аргументы возвращаются в качестве дополнительного словаря:

class MyArgumentParser(argparse.ArgumentParser):
    def parse_subargs(self, *args, **kw):
        # parse as usual
        args = argparse.ArgumentParser.parse_args(self, *args, **kw)

        # extract the destination names for top-level arguments
        topdest = [action.dest for action in parser._actions]

        # loop over all arguments given in args
        subargs = {}
        for key, value in args.__dict__.items():

            # if sub-parser argument found ...
            if key not in topdest:

                # ... remove from args and add to dictionary
                delattr(args,key)
                subargs[key] = value

        return args, subargs

Комментарии к этому подходу приветствуются, особенно любые лазейки, которые я пропустил.

Вам нужно войти в недра argparse немного, но изменение вашего сценария на следующее должно помочь:

import argparse
from argparse import _HelpAction, _SubParsersAction

class MyArgumentParser(argparse.ArgumentParser):
    def parse_args(self, *args, **kw):
        res = argparse.ArgumentParser.parse_args(self, *args, **kw)
        from argparse import _HelpAction, _SubParsersAction
        for x in parser._subparsers._actions:
            if not isinstance(x, _SubParsersAction):
                continue
            v = x.choices[res.parser_name] # select the subparser name
            subparseargs = {}
            for x1 in v._optionals._actions: # loop over the actions
                if isinstance(x1, _HelpAction): # skip help
                    continue
                n = x1.dest
                if hasattr(res, n): # pop the argument
                    subparseargs[n] = getattr(res, n)
                    delattr(res, n)
            res.subparseargs = subparseargs
        return res

parser = MyArgumentParser()
parser.add_argument("--verbose", default = 0, type=int)

subparsers = parser.add_subparsers(dest = "parser_name")

parser_lan = subparsers.add_parser('car')
parser_lan.add_argument("--boo")
parser_lan.add_argument("--foo")

parser_serial = subparsers.add_parser('bus')
parser_serial.add_argument("--fun")

print parser.parse_args()

Или вручную вы можете проанализировать аргументы и создать dict с подробностями:

      # parse args
args = parser.parse_args()
args_dict = {}
for group in parser._action_groups:
    # split into groups based on title
    args_dict[group.title] = {}
    for arg in group._group_actions:
        if hasattr(args, arg.dest):
            args_dict[group.title][arg.dest] = getattr(args, arg.dest)
            # or args_dict[arg.dest] = getattr(args, arg.dest)
            delattr(args, arg.dest)
# add remaining items into subparser options
args_dict["subparser"] |= vars(args)
return args_dict

Чтобы отфильтровать аргументы субпарсеров, вы можете использоватьparse_known_argsметод:

      car_subparser_args = parser_lan.parse_known_args()[0]
bus_subparser_args = parser_serial.parse_known_args()[0]
print(vars(car_subparser_args)) # -> {'boo': '1', 'foo': '2'}
print(vars(bus_subparser_args)) # -> {'fun': None}
Другие вопросы по тегам