How do I set an Argparse argument's default value to a positional argument's value?

17,640

Solution 1

nargs='?' handles this situation nicely. If there are only 2 strings, they get assigned to host and resource, and address gets its default (None). If 3 strings, they get assigned to all 3. If it helps, think of the behavior of ? in a re pattern.

It is easy to assign the host value to address after the parser is done. No point in trying to do that assignment within parse_args (since the host value won't be known yet).

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("address", nargs='?')
parser.add_argument("host")
parser.add_argument("resource")
args = parser.parse_args()

if args.address is None:
    args.address = args.host
print(args)

and the usage is:

usage: get.py [-h] [address] host resource

with the [] neatly marking the optional positional argument.

Solution 2

If you are trying to achieve the behaviour you described using ONLY positional arguments you can use a list argument (nargs) like this:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("args", nargs="+")
parsed = parser.parse_args()
args = parsed.args
if len(args) == 3:
    ip, host, address = args
    print ip, host, address
elif len(args) == 2:
    ip, host, address = args[0], args[0], args[1]
    print ip, host, address
else:
    print "Invalid args"

But not only is this hacky you also lose the benefits argparse provides (you have to manually verify the arguments). I recommend you use optional arguments. Perhaps like this:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-host", required=True)
parser.add_argument("-res", required=True)
parser.add_argument("-ip", required=False)
args = parser.parse_args()

ip = args.ip if args.ip else args.host
print args.host, args.res, ip

And execute it like this:

python2.7 test.py -host hello -res world

Solution 3

You have two reasonable choices here, and which you choose depends on how attached you are to using positional arguments as opposed to --options.

  1. Add a positional argument with nargs='+'. After parsing the args, check the length of that list. If it was 3, set them to address, host, and resource. If it was 2, set them to host and resource, and copy the address from host. Otherwise, print a usage message and raise an exception.

  2. Leave the host and resource as positional arguments. Change the address into an option. This means the CLI will instead be like:

python get.py stackoverflow.com /questions/ask --address 198.252.206.16

You can set the default value to None and then check if it is none after parsing the args, I don't think there is anyway in argparse to set it to automatically default to another positional arg.

Which you choose is personal preference, I like 2 better (optional arguments should be, by definition, options) because it is fitting the available tool more nicely - if you bend argparse to behave like 1 then you will have to additionally override the --help option, the usage message, etc because the autogenerated defaults will be misleading.

Share:
17,640
deau
Author by

deau

Updated on June 26, 2022

Comments

  • deau
    deau almost 2 years

    I have a python script that sends a GET request. It uses Argparse to take three arguments:

    1. Address: where to send the GET request
    2. Host: the host to declare in the GET request
    3. Resource: which resource is being requested

    An example usage might be:

    $ python get.py 198.252.206.16 stackoverflow.com /questions/ask

    In most cases, however, only the host and the resource need to be given as the host will resolve to the address:

     $ host -t a stackoverflow.com
     stackoverflow.com has address 198.252.206.16
    

    So desired usage might be:

    $ python get.py stackoverflow.com /questions/ask
    

    How do I set up Argparse so that the default value of the Address argument is the value of the Host argument?


    I've been asked to show the code that currently parses the arguments. Here it is:

    import argparse
    
    parser = argparse.ArgumentParser(description=
             'Send a GET request and obtain the HTTP response header and/or body.')
    
    parser.add_argument("-v", "--verbose",
                        help="Turn on verbose mode.",
                        action="store_true")
    parser.add_argument("-p", "--port",
                        type=int,
                        default=80,
                        help="Which port to use when sending the GET request."
    parser.add_argument("address",
                        help="Where to send the GET request to.")
    parser.add_argument("host",
                        help="Which Host to declare in the GET request.")   
    parser.add_argument("resource",
                        help="Which resource to request.")
    
    parser.parse_args()
    
  • deau
    deau over 9 years
    I agree, the address is an optional argument and a better man than me would submit to this logic; so I must +1; call me crazy though, 'cause I can't help but prefer @hpaulj's solution.
  • deau
    deau over 9 years
    Whilst I like the use of the clear required flag I think the result is a more cluttered command line than necessary. Thank you, though; it was a good idea.
  • deau
    deau over 9 years
    Thank you; not quite as logical as @wim's solution but a more elegant result; one which I'll be implementing :-)
  • wim
    wim over 9 years
    Actually, I prefer the solution of hpaulj too. I didn't know about this usage of nargs='?' before.
  • zyy
    zyy over 4 years
    I set a default with ap.add_argument('mode', type = int, default = 1, help = message) and it seems like only when I added nargs = '?' that the default will be taken in.