How to make argparse split nargs='+' from nargs=REMAINDER

Viewed 695

I am using argparse to parse several positional arguments as input and then several positional arguments as output. Since the number of arguments in not bounded, I am trying to use -- as a separator. I have typically been successful with the -- separator using nargs=argparse.REMAINDER, so I tried this:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("input", nargs='+')
parser.add_argument("output", nargs=argparse.REMAINDER)

args = parser.parse_args("input1 input2 -- output1 outpu2".split())
print(args)

However, all arguments are put into input and I get this output:

Namespace(input=['input1', 'input2', 'output1', 'outpu2'], output=[])

The -- is correctly consumed but it does not act as separator.

Is there a way to achieve this using argparse?

2 Answers

No, there isn't. -- is used to separate named ("optional") arguments from positional arguments, not to split positional arguments into subsegments. That's why the docs say (emphasis added; quote is from nargs='*' but there's no difference except that '+' requires at least one argument):

'*' All command-line arguments present are gathered into a list. Note that it generally doesn’t make much sense to have more than one positional argument with nargs='*', but multiple optional arguments with nargs='*' is possible.

Why don't you define a flag --output (with nargs='+' or even nargs='*') and invoke your program with the more explicit

input1 input2 --output output1 output2

(Use a short flagname if you're concerned about typing eight characters.)

You can define a custom action which takes care of dividing the set of arguments, using (almost) any delimiter you like, into input and output files.

import argparse


class PartitionedValues(argparse.Action):
    def __init__(self, *args, delim="+++", **kwargs):
        super().__init__(*args, **kwargs)
        self.delim = delimiter

    def __call__(self, parser, namespace, values, option_string):
        files = namespace.input = []
        namespace.output = []
        for f in values:
            if f == self.delim:
                files = namespace.output
            else:
                files.append(f)


p = argparse.ArgumentParser()
p.add_argument("files", action=PartitionedValues, nargs='+')
args = p.parse_args()
print(args.input)
print(args.output)

Then a call like

script.py file1 file2 +++ file3 file4

would produce

['file1', 'file2']
['file3', 'file4']

as output. (Note that -- itself, and apparently anything starting with --, would trigger the end of processing for the files parameter, so I used +++ as the default delimiter instead.)

I also made the delimiter configurable on a per-option basis. You could change the definition of the files argument to

p.add_argument("files", action=PartitionedValues, delim="+=+", nargs="+")

then call your script with

script.py file1 file2 +=+ file3 file4

to get the same output.

Related