firebush
firebush

Reputation: 5850

Python argparse: how to reference a parameter with a dash in it

I'm using argparse and I'd like to specify the positional argument with a dash in it. argparse seems to let me do this. Indeed, it shows up in the Namespace of parse_args(), but I cannot figure out how to reference the corresponding value. Here's a minimal example (notice the dash in 'a-string'):

#!/usr/bin/env python3

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('a-string', help='A string')

args = parser.parse_args()

# AttributeError: 'Namespace' object has no attribute 'a_string'
#print("Argument was: " + args.a_string)

# TypeError: 'Namespace' object is not subscriptable
#print("Argument was: " + args['a-string'])

# AttributeError: 'Namespace' object has no attribute 'a'
#print("Argument was: " + args.a-string)

# I give up. Ask StackOverflow.

I initially thought to address this with the dest argument to add_argument, but I get "ValueError: dest supplied twice for positional argument" if I add dest to a positional argument. How do I reference the value of this positional argument?

Upvotes: 6

Views: 5543

Answers (2)

hpaulj
hpaulj

Reputation: 231385

dest with a '-' is handled differently with an optional and a positional.

In [298]: import argparse                                                                            
In [299]: parser = argparse.ArgumentParser()                                                         
In [300]: a1 = parser.add_argument('--a-string');                                                 
In [301]: a2 = parser.add_argument('foo-string');                                                    
In [302]: parser.print_help()                                                                        
usage: ipython3 [-h] [--a-string A_STRING] foo-string

positional arguments:
  foo-string

optional arguments:
  -h, --help           show this help message and exit
  --a-string A_STRING

The dest for the optional is derived from the first 'long' flag, replacing any internal '-' with '_'. The dest for the positional is the first argument, without any replacements. You have complete control over the positional dest.

In [304]: args = parser.parse_args(['xxx'])                                                          
In [305]: args                                                                                       
Out[305]: Namespace(a_string=None, **{'foo-string': 'xxx'})
In [306]: args.a_string                                                                              

The dash dest can be still be accessed:

In [307]: vars(args)['foo-string']                                                                   
Out[307]: 'xxx'
In [308]: getattr(args, 'foo-string')                                                                
Out[308]: 'xxx'

The '-/_' replacement is done so you can use a '-' in the flag (a common POSIX practice), but still access the attribute with the dot syntax.

Internally argparse uses getattr and setattr so it does not limit the dest. It doesn't have to be a valid python name. This is true for optionals as well as positionals.

If I gave the optional a dest value (distinct from the flag),

In [310]: a1.dest = 'a-string'     # changing the existing argument
                                                              
In [312]: parser.print_help()                                                                        
usage: ipython3 [-h] [--a-string A-STRING] foo-string

positional arguments:
  foo-string

optional arguments:
  -h, --help           show this help message and exit
  --a-string A-STRING

This changed the 'A_STRING' to 'A-STRING', but also changed the attribute name:

In [313]: args = parser.parse_args(['xxx'])                                                          
In [314]: args                                                                                       
Out[314]: Namespace(**{'a-string': None, 'foo-string': 'xxx'})

metavar lets us control how the value is displayed in the help. Then we can choose the dest to be anything practical (or impractical if we prefer).

In [315]: a1.dest='a_string'; a1.metavar='MY-STRING'                                                 
In [316]: a2.dest='foo_bar'; a2.metavar='YOUR-STRING'                                                
In [317]: parser.print_help()                                                                        
usage: ipython3 [-h] [--a-string MY-STRING] YOUR-STRING

positional arguments:
  YOUR-STRING

optional arguments:
  -h, --help            show this help message and exit
  --a-string MY-STRING
In [318]: args = parser.parse_args(['xxx'])                                                          
In [319]: args                                                                                       
Out[319]: Namespace(a_string=None, foo_bar='xxx')

Ugly dest, independent of flags and metavar:

In [320]: a1.dest='a-#$'; a2.dest='-xxx$3'                                                           
In [321]: args = parser.parse_args(['xxx'])                                                          
In [322]: args                                                                                       
Out[322]: Namespace(**{'-xxx$3': 'xxx', 'a-#$': None})
In [323]: getattr(args, '-xxx$3')                                                                    
Out[323]: 'xxx'
In [324]: parser.print_usage()                                                                       
usage: ipython3 [-h] [--a-string MY-STRING] YOUR-STRING

Some have argued that positionals should perform the '-/_' replacement. But since the metavar gives full control over the help display, there's no need for that. At best if would just make positionals behave a bit more like `optionals.

Your options with the metavar are more limited. Special characters can mess up the usage formatting, especially if the usage is long (multiline) or with mutually exclusive groups. That formatting is rather brittle.

Upvotes: 1

khelwood
khelwood

Reputation: 59112

The arguments are given as attributes of the namespace object returned by parse_args(). But identifiers (including attributes) can't have a hyphen in them (at least not if you want to access them directly), because it's a minus sign, and that means something else.

If you want your argument to be named "a-string" to the user but accessible in your code as a_string (which was one of your attempted approaches) you can use the metavar argument to specify how it's described to the user.

parser.add_argument('a_string', help='A string', metavar='a-string')

args = parser.parse_args()
print(args.a_string)

The argument will appear in the usage information as:

positional arguments:
  a-string    A string

https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument

Upvotes: 10

Related Questions