Oli
Oli

Reputation: 239820

Argparse nargs="+" is eating positional argument

Here's a subsection of my parser configuration

parser.add_argument(
    'infile', help="The file to be imported",
    type=argparse.FileType('r'), default=sys.stdin
)

parser.add_argument(
    '--carpark', nargs='+', dest='CarparkID', type=int, default=[],
    help="One or many carpark IDs"
)

However, the --carpark argument seems to be too greedy and eats anything that follows it:

$ mycommand --carpark 17 ~/path-to-file
mycommand: error: argument --carpark: invalid int value: '/home/oli/path-to-file'

What's a good way around something like this? I need to pass a list of integer IDs into the command but also have a positional file (which can also be stdin).

Is there —for example— a non-greedy nargs option that will only parse as much of this as makes sense?

Upvotes: 6

Views: 3119

Answers (2)

chepner
chepner

Reputation: 531205

If you want to specify multiple car park IDs, I would do one of two things instead of using nargs='+':

  1. Use the option once per ID (mycommand --carpark 17 --carpark 18)

    parser.add_argument('--carpark',
                        dest='carpark_ids',
                        type=int,
                        action='append',
                        default=[],
                        help="One carpark ID (can be used multiple times)"
    )
    
  2. Take a single, comma-delimited argument instead (mycommand --carpark 17,18)

    def intlist(s):
        rv = []
        for x in s.split(','):
            try:
                x = int(x)
            except ValueError:
                raise ArgumentTypeError("Non-integer carpark id {x}" % (x,))
            rv.append(x)
        return rv
    
    parser.add_argument('--carpark',
                        type=intlist,
                        dest='carpark_ids',
                        default=[],
                        help="One or more carpark IDs"
    )
    

    With a little more work, you could modify this to allow multiple uses of --carpark to accumulate all its values into a single list.

A third alternative, one I'm not particularly fond of, is to abandon the positional argument, making it an optional argument instead. (mycommand --carpark 17 18 --infile ~/path-to-file).

parser.add_argument('--infile',
                    help="The file to be imported",
                    type=argparse.FileType('r'),
                    default=sys.stdin
)

parser.add_argument('--carpark',
                    nargs='+',
                    dest='CarparkID',
                    type=int,
                    default=[],
                    help="One or many carpark IDs"
)

Upvotes: 7

hpaulj
hpaulj

Reputation: 231395

Does this work?

$ mycommand ~/path-to-file --carpark 17 

There is a Python bug/issue over + Actions that consume too many arguments, leaving none for following Actions.

The argument allocation is based on argument counts, not on type. The type function is applied after allocation, and there's no provision for returning 'rejected' arguments.

In correct behavior it should take into account that infile is expecting an argument - and there aren't any further flag strings - and thus reserve one string to that Action.

I could look that bug/issue up, but for now the fix is to supply the arguments in a different order. Or define a --infile action (instead of the positional).

An earlier question, with the bug/issue link (my answer focuses more on getting the usage right).

Argparse - do not catch positional arguments with `nargs`.

Upvotes: 4

Related Questions