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.