Search code examples
pythonargumentsargparse

Positional argument after optional argument list


I'm somewhat new to using argparse and have run into a problem stemming from using an optional argument (list of files arbitrarily long) along with a positional argument (a single file). Here is a simple program that demonstrates this problem:

parser = argparse.ArgumentParser()
parser.add_argument("pos_file", help="Input file")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-l", "--optional_list", help="file list", nargs="+")
args = parser.parse_args()

When I call this program using the command:

python test_arg_parse.py -l file1.txt file2.txt pos_file.txt

Argparse fails, mentioning that there are too few arguments. I understand why this is happening - argparse assumes that pos_file.txt is part of the optional list and therefore doesn't find a positional argument - but I don't understand why argparse doesn't always consider the last argument to be positional. This can be solved by placing the verbose argument between the optional list and the positional argument:

python test_arg_parse.py -l file1.txt file2.txt -v pos_file.txt

Which argparse happily processes. However, I don't want to enforce this strict ordering of arguments and I would like to keep the list optional and the single file positional. I have looked into using the append functionality, but it doesn't seem reasonable considering the list can be arbitrarily long.

Is there a nice way of accomplishing this?


Solution

  • Parsing is driven by the order of values in the command line, having first identified which strings look like flags (optionals) and which are just arguments.

    python test_arg_parse.py -l file1.txt file2.txt pos_file.txt
    

    It first checks the start of the list for positional arguments - none. Then for flags - it finds '-l'. That takes '+', so it's given everything up to the next flag (or the end). Now there's nothing left on the list for 'pos_file'.

    There is a bug/issue about this, and I proposed a patch that would check the positionals, and hold back on giving the optional everything. But that requires too much fiddling with the core parsing code, and hasn't gotten very far in approval process.

    So for now, using a positional like this after an open ended argument (optionals or positionals) doesn't work

    python test_arg_parse.py pos_file.txt -l file1.txt file2.txt
    

    should work. But if you want more control, define pos_file as a flagged argument, not a positional.