pythonargparsesubparsers

Python Argparse subparsers


I am using the Argparse Module to parse command line options. I have a main script which calls either subscript A or subscript B. I now have a a subparser instance for A and B and the main parser instance which should contain info about variables that A and B need. Something like:

def __init__(self):
    parser = argparse.ArgumentParser("Parser")
    parser.addArgument('--input') #this should be availabe for A and B

    subparsers = parser.add_subparsers('Description')
    A_parser = subparsers.add_parser(A)
    A_paser.add_argument('--a_option')

    B_parser = parser.add_subparser('Description')
    B_parser.add_arguemnt('--b_option')

    args = parser.parse_args()

But if I use it like this, I can only change args.options via the --input option if I do not specify A or B on program call. Execution of

program.py A --input ./

or

program.py --input ./ A

both fail.


Solution

  • I think you messed up the example usage from the python documentation. I attempted to edit your code example, but there were so many typos that I gave up. Expecially the usage of add_subparsers() and add_parser() was mixed up.

    The cleaned up (and self-contained) version of the code looks like this:

    import argparse
    
    
    parser = argparse.ArgumentParser("Parser")
    parser.add_argument('--input') #this should be available for A and B
    
    subparsers = parser.add_subparsers(help='Description')
    A_parser = subparsers.add_parser('A')
    A_parser.add_argument('--a_option')
    
    B_parser = subparsers.add_parser('B')
    B_parser.add_argument('--b_option')
    

    Now you can call

    args = parser.parse_args(['--input', 'foo'])
    

    but also

    args = parser.parse_args(['--input', 'bar', 'A', '--a_option', 'a_option_arg'])
    

    and

    args = parser.parse_args(['--input', 'baz', 'B', '--b_option', 'b_option_arg'])
    

    Update

    In the comments, you ask:

    Is it possible that I can also pass a unknown number of arguments for the --input option and still have the A option after that? Something like: program.py --input ./ ../ ../../ A -a_option

    If you want to pass an unknown number of arguments to the --input option you need to pass nargs='*' to the argument definition of --input. However, you cannot use tags like A and B anymore to select your subparsers, because these would be taken as further arguments to --input.

    You can resort to parent parsers in that case. With parent parsers, you can merge the options of several parsers into one:

    import argparse
    
    
    A_parser = argparse.ArgumentParser(add_help=False)
    A_parser.add_argument('--a_option')
    
    B_parser = argparse.ArgumentParser(add_help=False)
    B_parser.add_argument('--b_option')
    
    parser = argparse.ArgumentParser("Parser", parents=[A_parser, B_parser])
    parser.add_argument('--input', nargs='*')  # this should be available for A and B
    

    Now all options are available every time:

    args = parser.parse_args(['--input', 'foo'])
    args = parser.parse_args(['--input', 'foo' , 'bar', '--a_option', 'a_option_arg'])
    args = parser.parse_args(['--input', 'baz', '--b_option', 'b_option_arg'])
    

    Note that there is no more 'A' and 'B' tag. Selecting 'A' occurs by choosing one of the options defined by A_parser. It is even allowed to use both:

    args = parser.parse_args(['--input', 'foo' ,'--a_option', 'a_option_arg', '--b_option', 'b_option_arg'])
    

    If you don't want this, you would need to implement a check for conflicting parameters.