How to obtain argparse subparsers from a parent parser (to inspect defaults)

argparse, arguments, parsing, python, subparsers

Solution

You got it right. But maybe I can explain a few details.

a = parser.add_argument(...)

`add_argument` creates an `Action` object (or actually a subclass depending on the `action` parameter). You can save a pointer to that object in your own environment. But that Action is also collected in the `parse._actions` list. That's how the `parser` keeps tracks of its arguments.

Reading `_actions` should always be safe. Modifying it risks breaking breaking the parser. `argument_groups` have access to the list.

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

is a specialized version of `add_argument`, creating and returning a `argparse._SubParsersAction` object. `subparsers` is that object. And as noted from the earlier answer, you can find it in the `_actions` list by searching for the correct subclass. (To the main parser, `subparsers` is just another positional argument.)

`subparsers` maintains its own specialized dictionary of `parsers`, accessible as its `choices` attribute. The main parser does not have any record of those sub parsers.

parser_other = subparsers.add_parser("other")

creates a parser, puts it in that `choices` map, and returns a reference for your own use (with `add_argument` etc). Each sub parser has its own `_actions` list. (and its own `_defaults`).

Look at the code for the `get_defaults` method:

def get_default(self, dest):
    for action in self._actions:
        if action.dest == dest and action.default is not None:
            return action.default
    return self._defaults.get(dest, None)

It uses the `_actions` attribute. And looks at the `action.default` attribute of the Action.

`self._defaults` is the dictionary updated by the `parser.set_defaults` method. That method also copies its parameters to the relevant Action objects. `get_defaults` checks that in case the `dest` is one of those defaults that isn't tied to a particular Action. https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.set_defaults

I haven't used the `parser._subparsers` attribute much. Looking at the `parser.add_subparsers` method I see it is actually an `argument_group`. Argument_groups are primarily a `help` tool, used to group help lines. The relationship between a parser object and its argument_groups is a little tricky, and probably not something you want to use.

Here's an example, with more (too much) detail:

In [22]: parser = argparse.ArgumentParser()
In [23]: sp = parser.add_subparsers(title='subparsers', dest='cmd')
In [24]: sp1 = sp.add_parser('cmd1')
In [25]: sp2 = sp.add_parser('cmd2')
In [26]: parser.print_help()
usage: ipython3 [-h] {cmd1,cmd2} ...

optional arguments:
  -h, --help   show this help message and exit

subparsers:
  {cmd1,cmd2}

In [28]: [a.dest for a in parser._actions]
Out[28]: ['help', 'cmd']

In [29]: parser._action_groups
Out[29]: 
[<argparse._ArgumentGroup at 0xaf86bf2c>,
 <argparse._ArgumentGroup at 0xaf86bdcc>,
 <argparse._ArgumentGroup at 0xac99fa6c>]
In [30]: [g.title for g in parser._action_groups]
Out[30]: ['positional arguments', 'optional arguments', 'subparsers']

In [31]: parser._subparsers
Out[31]: <argparse._ArgumentGroup at 0xac99fa6c>

The `_defaults` of `_subparsers` is actually the same dictionary as `parser._defaults`

In [32]: parser.set_defaults(extra='foobar')
In [33]: parser._defaults
Out[33]: {'extra': 'foobar'}
In [34]: parser._subparsers._defaults
Out[34]: {'extra': 'foobar'}

`parser._subparsers._actions` is also identical to `parser._actions`. But the group does maintain its own list actions (used in the help display).

In [35]: parser._subparsers._group_actions
Out[35]: [_SubParsersAction(option_strings=[], dest='cmd', nargs='A...', const=None, 
    default=None, type=None, choices=OrderedDict([...]), help=None, metavar=None)]

So you could use `parser._subparsers._group_actions[0]` to find the `subparsers` action object instead of searching the `parsers._actions` list.

In [37]: parser._subparsers._group_actions[0].choices
Out[37]: 
OrderedDict([('cmd1',
              ArgumentParser(prog='ipython3 cmd1', usage=None, description=None,...)),
             ('cmd2',
              ArgumentParser(prog='ipython3 cmd2', usage=None, description=None,...))])

On second thought, `parser._subparsers._group_actions` might not be so useful. If you don't give it a special title, then it is identical to `parser._positionals`, the argument group of all positional arguments. So you'd still need to verify the `_SubParsersAction` class.

Problem

Suppose that I create a parser with a default value for an argument, and then give it a subparser with a further default value for an argument. ``` In [1]: parser = argparse.ArgumentParser(description='test') In [2]: parser.add_argument("--test", dest="test", default="hello") Out[2]: _StoreAction(option_strings=['--test'], dest='test', nargs=None, const=None, default='hello', type=None, choices=None, help=None, metavar=None) In [3]: parser.get_default("test") Out[3]: 'hello' In [4]: subparsers = parser.add_subparsers(dest="command") In [5]: parser_other = subparsers.add_parser("other") In [6]: parser_other.add_argument("--other-test", dest="other_test", default="world") Out[6]: _StoreAction(option_strings=['--other-test'], dest='other_test', nargs=None, const=None, default='world', type=None, choices=None, help=None, metavar=None) In [7]: parser_other.get_default("other_test") Out[7]: 'world' ``` This is all fine. But suppose that I have a function which creates and returns the parent parser `parser` from above, but with no direct access to the subparser. How can I still print out defaults for the subparser arguments? Or get a handle to each subparser separately? ``` In [8]: parser._subparsers._defaults Out[8]: {} In [9]: parser._subparsers.get_default("other_test") # is None ``` There doesn't appear to be any more attributes or methods from `parser._subparsers` or from `parser` that could display defaults. The overall problem is: how to programmatically access subparser defaults when you only have a handle to the parent parser?

Original source

Related problems