Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In python, how to get subparsers to read in parent parser's argument?

Here is an example code:

import argparse

parser=argparse.ArgumentParser()
parser.add_argument('-main_arg')
subparser=parser.add_subparser()
a=subparser.add_parser('run')
a.add_argument('required_sub_arg')
a.add_argument('arg_a')
b=subparser.add_parser('b')
parser.parse_args()

I want it to read in -main_arg if I enter program run required_sub_arg -main_arg -arg_a

Right now, it doesn't recognize -main_arg as a valid argument.

like image 749
user e to the power of 2pi Avatar asked Aug 15 '11 15:08

user e to the power of 2pi


People also ask

What does parse_args return?

Adding arguments Later, calling parse_args() will return an object with two attributes, integers and accumulate . The integers attribute will be a list of one or more ints, and the accumulate attribute will be either the sum() function, if --sum was specified at the command line, or the max() function if it was not.

How do you make an argument mandatory in Python?

required is a parameter of the ArugmentParser object's function add_argument() . By default, the arguments of type -f or --foo are optional and can be omitted. If a user is required to make an argument, they can set the keyword argument required to True .

How do you add an optional argument in Argparse?

To add an optional argument, simply omit the required parameter in add_argument() . args = parser. parse_args()if args.


2 Answers

PSA to recent readers

As this question still has visits in 2018, before doing anything this complex with argparse, please consider using docopt or click instead. It will improve both your sanity and that of anyone who might read or modify your code. Thank you.

Original answer

As is, you have a few issues.

First, parser.parse_args is a method that returns a namespace of parser's arguments, so you should do something like

args = parser.parse_args()

Then args.main_args to get-main_arg from a call like

program -main_arg run required_sub_arg -arg_a

Your issue with main_arg is that you have created a argument to parser named main_arg, and you make a call like

program run required_sub_arg -main_arg -arg_a

that refers to an argument to a named main_arg. Since a doesn't have such an argument, it is invalid.

In order to refer to a parser's argument from one of its subparser, you have to make said subparser inherit the arguments of its parent. This is done with

a=parser.add_subparser('run', parents=[parser])

You have mistaken subparser for child parser. See http://docs.python.org/dev/py3k/library/argparse.html and https://code.google.com/p/argparse/issues/detail?id=54 for more informations.

like image 155
Evpok Avatar answered Sep 19 '22 15:09

Evpok


For anyone else using argparse that arrives here looking for a way to display "common" sub-parser arguments in the "main" help screen, here's one approach:

import argparse
common = argparse.ArgumentParser(add_help=False)
common.add_argument('--shared', action='store_true', help='some shared arg')
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--parent', action='store_true', help='parent only arg')
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[common])
run.add_argument('--fast', action='store_true', help='run only arg')
parser.epilog = "--- Arguments common to all sub-parsers ---" \
    + common.format_help().replace(common.format_usage(), '')
args = parser.parse_args()

Main help:

$ program.py -h
usage: program.py [-h] {run} ...

positional arguments:
  {run}

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

--- Arguments common to all sub-parsers ---
optional arguments:
  --shared  some shared arg

run sub-parser help:

$ program.py run -h
usage: program.py run [-h] [--shared]

optional arguments:
  -h, --help  show this help message and exit
  --shared    some shared arg
  --fast      run only arg

To address the actual question, since the accepted answer doesn't run for me, here's some additional information on why it doesn't seem possible to truly share argparse arguments with the same name across both parent and child/sub-parser parsers.

First, the problem with the following code:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-main_arg')
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[parser])
args = parser.parse_args()

Is that it leads to the following error, because both parent parser and sub-parser run define the -h/--help argument (by default).

Argparse.ArgumentError: argument -h/--help: conflicting option strings: -h, --help

While this error can be avoided by suppressing the -h/--help option (with add_help=False) on either the parent or the child, it's nice to have the help option at both levels.

Another potential way to avoid conflicting help options is to move common arguments to a shared parser, common:

import argparse
common = argparse.ArgumentParser(add_help=False)
common.add_argument('-main_arg', action='store_true')
parser = argparse.ArgumentParser(parents=[common])
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[common])
args = parser.parse_args()
print(args)

While this appears to work on the surface, in practice, it doesn't work as intended:

$ program.py run  # OK
Namespace(main_arg=False)
$ program.py run -main_arg  # OK
Namespace(main_arg=True)
$ program.py -main_arg run  # BAD: expected main_arg to be True
Namespace(main_arg=False)

The behavior observed when parsing program.py -main_arg run illustrates a key relationship: a parent argparser and its sub-parsers are independent parsers, where the parent parses all arguments up to the sub-parser "command" positional argument, and then the selected sub-parser parses the remaining arguments in the same Namespace as the parent with no regard for attributes that may have been set by the parent.

like image 41
Garrett Avatar answered Sep 20 '22 15:09

Garrett