0

I want to have a set of arguments be passed into a script with an equal amount of inputs and outputs arguments. I know that I can parse along the lines of

inputs, outputs = sys.argv[:halfway], sys.argv[halfway:]

taking into account sys.argv[0] being the name, but I want the helpful features of argparse.

I also know that I can change the code to parser.add_argument('-i', 'inputs', nargs='+') so that I can specify my arguments as python testarg.py -i 1 2 -o 3 4, but I do not want to use that syntax as there is already a precedent of one-input, one-output python testarg.py input output which I would like to keep by making the syntax python testarg.py inputs[...] outputs[...]

This is the closest I get

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('inputs', nargs='+')
parser.add_argument('outputs', nargs='+')
print(parser.parse_args())
$ python testarg.py 1
usage: testarg.py [-h] input [input ...] output [output ...]
testarg.py: error: the following arguments are required: output

$ python testarg.py 1 2
Namespace(inputs=['1'], outputs=['2'])

$ python testarg.py 1 2 3 4
Namespace(inputs=['1', '2', '3'], outputs=['4'])

I want

Namespace(inputs=['1', '2'], outputs=['3', '4'])
4
  • 1
    I think it doesn't have function for this and it can be much simpler to use -i and -o Commented Sep 17, 2020 at 23:12
  • How do the inputs and outputs work? I'm thinking it might make more sense to pair them up, like testarg.py input output [--io input output] .... That would let you use nargs=2 to validate the numbers for you. Commented Sep 17, 2020 at 23:18
  • 1
    input output [input output]... is another approach that would be significantly less work to parse (just have argparse read one list, then check that it has an even number of items and report it as a usage error otherwise -- something there's a convenient method for). Commented Sep 17, 2020 at 23:18
  • Thank you, pairs of input output [input output]... is perhaps an easier way, I didn't think about it! Commented Sep 18, 2020 at 22:17

2 Answers 2

2

The click library can do this, it supports callback functions that can modify argument values.

import click


def split_args(context, param, value):
    num_args = len(value)
    if num_args % 2:
        raise click.BadParameter(
            f"Must provide an even number of arguments, got {num_args} arguments"
        )
    midpoint = num_args // 2
    return value[:midpoint], value[midpoint:]


@click.command()
@click.argument("args", callback=split_args, nargs=-1)
def io(args):
    inputs, outputs = args
    print("inputs: ", inputs)
    print("outputs: ", outputs)


if __name__ == "__main__":
    io()
$ python3 testarg.py 1 2 3 4
inputs:  ('1', '2')
outputs:  ('3', '4')
Sign up to request clarification or add additional context in comments.

Comments

1

The nargs are modelled on (and even use) the regex wildcard quantifiers

In this case:

Namespace(inputs=['1', '2', '3'], outputs=['4'])

one value has been allocated to outputs (because it is "one-or-more"), and the rest, the "more" goes to inputs.

Now if you could accept

python prog.py --inputs 1 2 --outputs 3 4

the '+' would work as expected.

But with variable length positionals (or optional followed by positional), there's no way to tell it where the first list ends and second starts.

Of course if you like the argparse help, you could adjust the lists balance after parsing - e.g move the '3' to the other list. Nothing wrong with tweaking the parsed values. You won't get extra "good boy" points for doing everything in the parser itself.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.