Reputation: 5850
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
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
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