Variable Number of narg Arguments Python Argparse

1.1k views Asked by At

This is likely beyond the scope of the argparse module, but I'll try to describe my issue using an example.

I have some fruits and some files attributed to each fruit. Specifically Apple, Banana, and Orange. Apple has 10 files associated with it, Banana has 7, and Oranges has 9. I can hardcode -a, -b, -o each taking nargs='+' to handle this toy example in a Python command-line script. But say I have a variable number of fruits, or a very large number (maybe 50). It would be crazy to hardcode a flag for each type, what is the best solution here?

2

There are 2 answers

1
Robᵩ On BEST ANSWER

I would create an argument for each of the fruits, but I would do it in the DRYest way I could:

import argparse

parser = argparse.ArgumentParser()

parser.add_argument("-i", "--insecticide")
for fruit, nargs in (
    ('Apple', 10),
    ('Banana', 7),
    ('Orange', 9),
    ):
    parser.add_argument(
        "--" + fruit.lower(), nargs=nargs, metavar='FLIES', 
        help="specify {} species of {} pests".format(nargs, fruit))
args = parser.parse_args()
print(args)

Here is the resulting help message:

$ python x.py -h
usage: x.py [-h] [-i INSECTICIDE]
            [--apple FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES]
            [--banana FLIES FLIES FLIES FLIES FLIES FLIES FLIES]
            [--orange FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES]

optional arguments:
  -h, --help            show this help message and exit
  -i INSECTICIDE, --insecticide INSECTICIDE
  --apple FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES
                        specify 10 species of Apple pests
  --banana FLIES FLIES FLIES FLIES FLIES FLIES FLIES
                        specify 7 species of Banana pests
  --orange FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES FLIES
                        specify 9 species of Orange pests

If there were a variable number of fruits (controlled by an environment variable, or the presence of configuration files, for example), then my loop wouldn't be hardcoded, but instead it might be:

for config_section in config_data():
    parser.add_argument("--" + config_section.name, ...)

If I wanted to name the parameters sequentially, as OP's comment suggests, I might code the loop:

for i, fruit in enumerate(fruit_names, 1):
    parser.add_argument("--x{}".format(i), 
                        nargs="+", 
                        help="{} files".format(fruit))

And here is the resulting help message:

$ python x2.py -h
usage: x2.py [-h] [-i INSECTICIDE] [--x1 X1 [X1 ...]] [--x2 X2 [X2 ...]]
             [--x3 X3 [X3 ...]]

optional arguments:
  -h, --help            show this help message and exit
  -i INSECTICIDE, --insecticide INSECTICIDE
  --x1 X1 [X1 ...]      Apple files
  --x2 X2 [X2 ...]      Banana files
  --x3 X3 [X3 ...]      Orange files
1
Cade Brown On

You can use nargs=7 for example, and it will only accept exactly 7 (or return None if the flag is not entered)

For example, add

parser.add_argument('-x', nargs=7, help='testing')

to your argument list and print args.x

Say your file was a.py

$ python a.py

> None

$ python a.py -x 1

> a.py: error: argument -x: expected 7 argument(s)

$ python a.py -x 1 2 3 4 5 6 7

> ['1', '2', '3', '4', '5', '6', '7'] `