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?
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With