3

I'm using python 2.7.13.

My goal is to have three possible arguments, with default values being set if no arguments are given by the user:

parser.add_argument("-r", nargs=3, default=(0, 1000, 50), type=int, help="Useful help text")

This doesn't work for me, and I can't find anywhere if it is possible to use default in such a way as above.

When running it as program.py -r I get a an error: expected 3 argument(s)

But I also tried removing nargs completely and only having one default value:

parser.add_argument("-r", default=100)

Strangely enough, this doesn't work either. It requires at least one argument...

Anyone understand this?

1
  • If you execute with program.py -r the argparse expects some parameters, if you skip the -r flag it will use default values. Commented Mar 21, 2017 at 13:28

4 Answers 4

4

I'll illustrate the normal behavior of default in argparse (with a Ipython interactive session)

In [32]: parser = argparse.ArgumentParser()

Define 3 Actions:

In [33]: parser.add_argument('-r', nargs=3, type=int, default=(1,2,3));
In [35]: parser.add_argument('-f', default='DEFAULT');
In [37]: parser.add_argument('-g', nargs='?', default='DEFAULT', const='const');

The help. Note that all Actions have [], indicating that they are optional:

In [39]: parser.print_help()
usage: ipython3 [-h] [-r R R R] [-f F] [-g [G]]

optional arguments:
  -h, --help  show this help message and exit
  -r R R R
  -f F
  -g [G]

If called without any argments, all of the defaults appear in the args namespace.

In [40]: parser.parse_args([])  # e.g python myprog.py
Out[40]: Namespace(f='DEFAULT', g='DEFAULT', r=(1, 2, 3))

Giving -r with 3 numbers (as specified by the nargs)

In [41]: parser.parse_args('-r 4 5 6'.split())  
Out[41]: Namespace(f='DEFAULT', g='DEFAULT', r=[4, 5, 6])

Specify one of the other flags. Note the remaining defaults

In [42]: parser.parse_args('-f other'.split())  
Out[42]: Namespace(f='other', g='DEFAULT', r=(1, 2, 3))

-g with nargs='?' has another option. It can be given without arguments. In that case it gets the const value.

In [43]: parser.parse_args('-f other -g'.split())  
Out[43]: Namespace(f='other', g='const', r=(1, 2, 3))
In [44]: parser.parse_args('-f other -g more'.split())  
Out[44]: Namespace(f='other', g='more', r=(1, 2, 3))

There isn't such a 3 way option for nargs=3. You either provide the 3 values, or you don't use -r. If you need to distinguish between 1) no-r flag, 2) r flag without arguments, and 3) r flat with 3 arguments, I'd suggest splitting functionality into 2 actions, one a 'store_true', and other that takes the 3 values.

Defaults in argparse can be a complicated matter, with various ways of setting them, differences between string and non-string values, even a way of suppressing them. But I've shown the basic behavior.

Sign up to request clarification or add additional context in comments.

Comments

1

You only need to call the argument -r if you are not using the default value. If you do call then you have to also pass a value (or as many values you define in nargs).

Try just calling program.py

5 Comments

Okay, but what do I do if I want to be able to use -r no matter what? Since I have other flags also, I want to be able to use -r with or without arguments. And of course, having default values if no arguments were given. Is this possible?
What is -r without arguments supposed to do?
When you call program.py without flags you imply that you want default values for all flags. If you didn't, you should have said so ;) So, if you want to use -r with default values just don't mention it and let argarse silently assign it its default value. If for some reason you just have to mention -r just pass it its default value explicitly.
Sorry, I should have been more specific :) I do have to mention -r because it performs a specific function. The puprose is to give the user the possibility to add a range eg. -r 0 1000, but if this range is excluded I need that range to be set to default values. So how do I pass its default values explicitly?
When you call program.py without flags, r will be set to 100 by default. If, for some arcane reason you need to explicitly type -r in the console, you can call the default value by calling program.py -r 100. Or, if you need to pass it a list you do program.py -r 0 1000 50. If you also have another flag you can call program.py -r 0 1000 50 -other_flag 'value'.
0

Why don't you use argv?

Example:

import sys

def main(first=1, second=2, third=3):
    print first,second,third

if __name__ == '__main__':
    main(*sys.argv[1:])       # notice the * before sys.argv[1:]

You can test executing the file from console with different number of parameters:

python myprogram.py          # will print 1 2 3
python myprogram.py 7        # will print 7 2 3
python myprogram.py 7 8      # will print 7 8 3
python myprogram.py 7 8 9    # will print 7 8 9

1 Comment

What does the * before sys.argv represent?
0

With your example you can either call the application without the -r flag which would result in the default values. If you want to specify the values yourself you have to give them all.

If you would like to enter them independently you could try something like this:

# script.py
import argparse

def main1():
    parser = argparse.ArgumentParser()
    parser.add_argument("-r", nargs=3, default=(0, 1000, 50), type=int,
                        help="Useful help text")
    args = parser.parse_args()
    print(args)

def main2():
    parser = argparse.ArgumentParser()
    parser.add_argument("-r1", type=int, default=0, help="...")
    parser.add_argument("-r2", type=int, default=1000, help="...")
    parser.add_argument("-r3", type=int, default=50, help="...")
    args = parser.parse_args()
    print(args)

if __name__ == "__main__":
    main1()
    #main2()

You could call your implementation (main1) like this:

$ python script.py
Namespace(r=(0, 1000, 50))
$ python script.py -r 1 2 3
Namespace(r=[1, 2, 3])
$ python script.py -r 1
usage: args.py [-h] [-r R R R]
args.py: error: argument -r: expected 3 arguments

Using separate arguments your invocations could be looking like this (main2):

$ python script.py -r1 1 -r2 2 -r3 3
Namespace(r1=1, r2=2, r3=3)
$ python script.py -r1 1
Namespace(r1=1, r2=1000, r3=50)
$ python script.py -r2 2
Namespace(r1=0, r2=2, r3=50)
$ python script.py -r3 3
Namespace(r1=0, r2=1000, r3=3)

This could be a little bit more wordy if you want to change all values but it gives you more flexibility. I think you could even combine those two approaches using a mutually_exclusive_group.

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.