argparse - Combining parent parser, subparsers and default values

argparse, python

Solution

`set_defaults` loops through the actions of the parser, and sets each `default` attribute:

   def set_defaults(self, **kwargs):
        ...
        for action in self._actions:
            if action.dest in kwargs:
                action.default = kwargs[action.dest]

Your `-n` argument (an `action` object) was created when you defined the `base_parser`. When each subparser is created using `parents`, that action is added to the `._actions` list of each subparser. It doesn't define new actions; it just copies pointers.

So when you use `set_defaults` on `subparser2`, you modify the `default` for this shared action.

This Action is probably the 2nd item in the `subparser1._action` list (`h` is the first).

 subparser1._actions[1].dest  # 'n'
 subparser1._actions[1] is subparser2._actions[1]  # true

If that 2nd statement is `True`, that means the same `action` is in both lists.

If you had defined `-n` individually for each subparser, you would not see this. They would have different action objects.

I'm working from my knowledge of the code, not anything in the documentation. It was pointed out recently in Cause Python's argparse to execute action for default that the documentation says nothing about `add_argument` returning an `Action` object. Those objects are an important part of the code organization, but they don't get much attention in the documentation.

Copying parent actions by reference also creates problems if the 'resolve' conflict handler is used, and the parent needs to be reused. This issue was raised in

argparse conflict resolver for options in subcommands turns keyword argument into positional argument

and Python bug issue:

http://bugs.python.org/issue22401

A possible solution, for both this issue and that, is to (optionally) make a copy of the action, rather than share the reference. That way the `option_strings` and `defaults` can be modified in the children without affecting the parent.

Problem

I wanted to define different subparsers in a script, with both inheriting options from a common parent, but with different defaults. It doesn't work as expected, though. Here's what I did: ``` import argparse # this is the top level parser parser = argparse.ArgumentParser(description='bla bla') # this serves as a parent parser base_parser = argparse.ArgumentParser(add_help=False) base_parser.add_argument('-n', help='number', type=int) # subparsers subparsers = parser.add_subparsers() subparser1= subparsers.add_parser('a', help='subparser 1', parents=[base_parser]) subparser1.set_defaults(n=50) subparser2 = subparsers.add_parser('b', help='subparser 2', parents=[base_parser]) subparser2.set_defaults(n=20) args = parser.parse_args() print args ``` When I run the script from the command line, this is what I get: ``` $ python subparse.py b Namespace(n=20) $ python subparse.py a Namespace(n=20) ``` Apparently, the second `set_defaults` overwrites the first one in the parent. Since there wasn't anything about it in the argparse documentation (which is pretty detailed), I thought this might be a bug. Is there some simple solution for this? I could check the `args` variable afterwards and replace `None` values with the intended defaults for each subparser, but that's what I expected argparse to do for me. This is Python 2.7, by the way.

Original source

Related problems