pythonargparse

Configure python argparse to accept an optional argument multiple times, like `grep --exclude`


How do I configure a python argparse object to accept an optional argument multiple times, similar to how I can pass --exclude to grep mutliple times?

# Command line example
python main.py --exclude=foo.js --exclude=bar.java  # args.exclude should be ['foo.js', 'bar.java']

python main.py --exclude=foo.js                     # args.exclude should be ['foo.js']
python main.py                                      # args.exclude should be []

python main.py --exclude foo.js bar.java            # Reject this, or don't put bar.java in list
python main.py --exclude                            # Reject this

I tried this code, but it only accepts the last --exclude argument. The previous ones are discarded.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument( '--exclude', type=str, nargs='*', default=[], help='Something to exclude')

args = parser.parse_args('--exclude=foo.js --exclude=bar.java'.split(' '))
print(args.exclude)
# output 
['bar.java']             # actual output
['foo.js', 'bar.java']   # desired output

Additional requirements

  1. --exclude should be optional. args.exclude should be an empty list if exclude isn't supplied
  2. If --exclude is supplied, it should have 1 and only 1 argument.

Solution

  • I get the exact behavior I want in python 3.11 by omitting the nargs argument, and adding action='append'. Here is a complete test app

    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument( '--exclude', type=str, action='append', default=[], help='Something to exclude')
    
    test_inputs = [
        '--exclude=foo.js --exclude=bar.java',
        '',
        '--exclude=foo.js bar.java',
        '--exclude'
    ]
    
    for cmdline in test_inputs:
        print(f'"{cmdline}" parsed as : ', end='', flush=True)
        args = parser.parse_args(cmdline.split(' ') if cmdline else [])
        print (args.exclude, flush=True)
    
    # output
    "--exclude=foo.js --exclude=bar.java" parsed as : ['foo.js', 'bar.java']
    "" parsed as : []
    "--exclude=foo.js bar.java" parsed as : usage: main.py [-h] [--exclude EXCLUDE]
    main.py: error: unrecognized arguments: bar.java
    "--exclude" parsed as : usage: main.py [-h] [--exclude EXCLUDE]
    main.py: error: argument --exclude: expected one argument
    

    Note that if you run this, the app actually aborts after the first parse error. You have to comment out that input to see the last error.