39

I have an optional argument that supports a list of arguments itself.

I mean, it should support:

  • -f 1 2
  • -f 1 2 3

but not:

  • -f 1
  • -f 1 2 3 4

Is there a way to force this within argparse ? Now I'm using nargs="*", and then checking the list length.

Edit: As requested, what I needed is being able to define a range of acceptable number of arguments. I mean, saying (in the example) 2 or 3 args is right, but not 1 or 4 or anything that's not inside the range 2..3

Doppelganger
  • 20,114
  • 8
  • 31
  • 29
  • I don't understand. Do you mean that you want to be able to support e.g. between one and ten arguments? – Katriel Nov 16 '10 at 14:08
  • 5
    Could you please elaborate more on what the rules are? You want to require at least 2, and at most 3 values? Do they have to be 1-4 or can they be anything? – slf Nov 16 '10 at 14:09

1 Answers1

31

You could do this with a custom action:

import argparse

def required_length(nmin,nmax):
    class RequiredLength(argparse.Action):
        def __call__(self, parser, args, values, option_string=None):
            if not nmin<=len(values)<=nmax:
                msg='argument "{f}" requires between {nmin} and {nmax} arguments'.format(
                    f=self.dest,nmin=nmin,nmax=nmax)
                raise argparse.ArgumentTypeError(msg)
            setattr(args, self.dest, values)
    return RequiredLength

parser=argparse.ArgumentParser(prog='PROG')
parser.add_argument('-f', nargs='+', action=required_length(2,3))

args=parser.parse_args('-f 1 2 3'.split())
print(args.f)
# ['1', '2', '3']

try:
    args=parser.parse_args('-f 1 2 3 4'.split())
    print(args)
except argparse.ArgumentTypeError as err:
    print(err)
# argument "f" requires between 2 and 3 arguments
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 2
    Time traveler here. Great answer! However, you're subclassing `argparse.Action` here... what happens if I want my action to be `append`? How can I modify this code so that I can append multiple instances of this argument, and also get that specified range effect? – 2rs2ts Jul 08 '13 at 22:35
  • 1
    Ah, actually, merging your code with [this answer](http://stackoverflow.com/a/5374229/691859) did what I was looking for. Never mind me - unless you were thinking the same thing, in which case, check that answer out. – 2rs2ts Jul 08 '13 at 23:52
  • 4
    There is a proposed patch http://bugs.python.org/issue11354 to add a range `nargs` option, e.g. `nargs=(2,3)` or `nargs='{2,3}'` (`re` style notation). That approach is more powerful when other positionals have variable `nargs` values. Otherwise this custom action approach works fine. – hpaulj Jul 31 '13 at 20:53
  • 8
    It's 2018 and it's still under patch review. – huggie Aug 31 '18 at 08:08
  • Almost 2019 and still under review. A simple solution to appending (but a very dirty one) would be to extend `argparse._AppendAction` and use `super.__call__()` at the end of your call method. – Mad Physicist Dec 31 '18 at 07:15
  • 7
    Now 2020 and it was closed because not enough people want it. – Gavin S. Yancey Jan 13 '20 at 22:27
  • I think it would be more suitable to do this with a `custom type`, so that actions like `'append'` could be used together. – dojuba Oct 01 '21 at 07:59