AltoBalto
AltoBalto

Reputation: 401

Python - Circumvent argparse nargs error

I have a program that works like this:

prog.py filename -r

Uses default default values given by me

prog.py filename -r 0 500 20

Uses 0, 500 and 20

I've managed to achieve this using:

class RdistAction(argparse.Action):
    def __call__(self,parser,namespace,values,option_string=None):
        if not values:
            setattr(namespace,self.dest,[0, 1000, 50])
        else:
            setattr(namespace,self.dest,values)

parser = argparse.ArgumentParser()
parser.add_argument("-r", "--rdist", action=RdistAction, nargs='*', type=int)
args = parser.parse_args()

But I want to be stubborn, as my original goal was to have nargs set to 3. But when I use nargs=3 in the above code, I get an error message stating that 3 arguments were expected.

I've googled around, and from the results my gut tells me that I have to add def __init__ and modify something in that function. Is it possible to get the same results I get in the above code when nargs='*', but with nargs=3 instead?

Upvotes: 11

Views: 10385

Answers (2)

hpaulj
hpaulj

Reputation: 231375

As I pointed out in your previous question, simply omitting the -r puts the default in the args Namespace. You don't have to do anything special.

nargs='?' has a third option, const.

Your custom action acts a bit like this, in that it assigns a custom value to the Namespace when -r was used without any strings.

Parsing goes roughly as:

  • assign defaults to namespace
  • loop through the inputs, processing positionals and options in order
  • for each argument, call its Action.__call__
  • the number of values passed depends on the nargs parameter

Your custom RdistAction has no control over the number of values, except via its nargs attribute. nargs=3 means exactly 3, nargs='*' means 'as many as you can give me' (i.e. 0 or to the end or to the next flag).

In optparse an Action is give all the remaining argv strings, and can consume as many as it wants. But in argparse, the allocation is done at a higher level, and the Action uses what it's given.

I have explored in Python bug/issue expanding nargs to include regex like ranges, e.g. [0,3]. But that requires changes to the core of the parser.

You still haven't explained why you need this bare -r option.

Upvotes: 0

Sam Mussmann
Sam Mussmann

Reputation: 5983

If I change your add_argument line to have nargs='3', I think I get the error you're taking about:

Traceback (most recent call last):
  File "python", line 12, in <module>
ValueError: length of metavar tuple does not match nargs

If I set nargs=3 (without the quotes), then it works for me:

import argparse

class RdistAction(argparse.Action):
    def __call__(self,parser,namespace,values,option_string=None):
      if not values:
        setattr(namespace,self.dest,[0, 1000, 50])
      else:
        setattr(namespace,self.dest,values)

parser = argparse.ArgumentParser()
parser.add_argument("-r", "--rdist", action=RdistAction, nargs=3, type=int)
print parser.parse_args('-r 0 500 20'.split())

gives

Namespace(rdist=[0, 500, 20])

Is that what you're looking for?

The trick here is that if nargs isn't one of the special characters ('?', '*', '+', etc), then it needs to be an integer, not a string.

Note that the documentation does point this out.

Upvotes: 24

Related Questions