For optional argument actions, the value of ``dest`` is normally inferred from
the option strings. :class:`ArgumentParser` generates the value of ``dest`` by
-taking the first long option string and stripping away the initial ``--``
-string. If no long option strings were supplied, ``dest`` will be derived from
+taking the first double-dash long option string and stripping away the initial
+``-`` characters.
+If no double-dash long option strings were supplied, ``dest`` will be derived
+from the first single-dash long option string by stripping the initial ``-``
+character.
+If no long option strings were supplied, ``dest`` will be derived from
the first short option string by stripping the initial ``-`` character. Any
internal ``-`` characters will be converted to ``_`` characters to make sure
the string is a valid attribute name. The examples below illustrate this
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-f', '--foo-bar', '--foo')
+ >>> parser.add_argument('-q', '-quz')
>>> parser.add_argument('-x', '-y')
- >>> parser.parse_args('-f 1 -x 2'.split())
- Namespace(foo_bar='1', x='2')
- >>> parser.parse_args('--foo 1 -y 2'.split())
- Namespace(foo_bar='1', x='2')
+ >>> parser.parse_args('-f 1 -q 2 -x 3'.split())
+ Namespace(foo_bar='1', quz='2', x='3')
+ >>> parser.parse_args('--foo 1 -quz 2 -y 3'.split())
+ Namespace(foo_bar='1', quz='2', x='2')
``dest`` allows a custom attribute name to be provided::
>>> parser.parse_args('--foo XXX'.split())
Namespace(bar='XXX')
+.. versionchanged:: next
+ Single-dash long option now takes precedence over short options.
+
.. _deprecated:
Use its :meth:`!close` method or the :func:`contextlib.closing` context
manager to close it.
(Contributed by Osama Abdelkader and Serhiy Storchaka in :gh:`140601`.)
+
+* If a short option and a single-dash long option are passed to
+ :meth:`argparse.ArgumentParser.add_argument`, *dest* is now inferred from
+ the single-dash long option. For example, in ``add_argument('-f', '-foo')``,
+ *dest* is now ``'foo'`` instead of ``'f'``.
+ Pass an explicit *dest* argument to preserve the old behavior.
+ (Contributed by Serhiy Storchaka in :gh:`138697`.)
def _get_optional_kwargs(self, *args, **kwargs):
# determine short and long option strings
option_strings = []
- long_option_strings = []
for option_string in args:
# error on strings that don't start with an appropriate prefix
if not option_string[0] in self.prefix_chars:
raise ValueError(
f'invalid option string {option_string!r}: '
f'must start with a character {self.prefix_chars!r}')
-
- # strings starting with two prefix characters are long options
option_strings.append(option_string)
- if len(option_string) > 1 and option_string[1] in self.prefix_chars:
- long_option_strings.append(option_string)
# infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
dest = kwargs.pop('dest', None)
if dest is None:
- if long_option_strings:
- dest_option_string = long_option_strings[0]
- else:
- dest_option_string = option_strings[0]
- dest = dest_option_string.lstrip(self.prefix_chars)
+ priority = 0
+ for option_string in option_strings:
+ if len(option_string) <= 2:
+ # short option: '-x' -> 'x'
+ if priority < 1:
+ dest = option_string.lstrip(self.prefix_chars)
+ priority = 1
+ elif option_string[1] not in self.prefix_chars:
+ # single-dash long option: '-foo' -> 'foo'
+ if priority < 2:
+ dest = option_string.lstrip(self.prefix_chars)
+ priority = 2
+ else:
+ # two-dash long option: '--foo' -> 'foo'
+ dest = option_string.lstrip(self.prefix_chars)
+ break
if not dest:
- msg = f'dest= is required for options like {option_string!r}'
+ msg = f'dest= is required for options like {repr(option_strings)[1:-1]}'
raise TypeError(msg)
dest = dest.replace('-', '_')
class TestOptionalsDest(ParserTestCase):
"""Tests various means of setting destination"""
- argument_signatures = [Sig('--foo-bar'), Sig('--baz', dest='zabbaz')]
+ argument_signatures = [
+ Sig('-x', '-foobar', '--foo-bar', '-barfoo', '-X'),
+ Sig('--baz', dest='zabbaz'),
+ Sig('-y', '-qux', '-Y'),
+ Sig('-z'),
+ ]
failures = ['a']
successes = [
- ('--foo-bar f', NS(foo_bar='f', zabbaz=None)),
- ('--baz g', NS(foo_bar=None, zabbaz='g')),
- ('--foo-bar h --baz i', NS(foo_bar='h', zabbaz='i')),
- ('--baz j --foo-bar k', NS(foo_bar='k', zabbaz='j')),
+ ('--foo-bar f', NS(foo_bar='f', zabbaz=None, qux=None, z=None)),
+ ('-x f', NS(foo_bar='f', zabbaz=None, qux=None, z=None)),
+ ('--baz g', NS(foo_bar=None, zabbaz='g', qux=None, z=None)),
+ ('--foo-bar h --baz i', NS(foo_bar='h', zabbaz='i', qux=None, z=None)),
+ ('--baz j --foo-bar k', NS(foo_bar='k', zabbaz='j', qux=None, z=None)),
+ ('-qux l', NS(foo_bar=None, zabbaz=None, qux='l', z=None)),
+ ('-y l', NS(foo_bar=None, zabbaz=None, qux='l', z=None)),
+ ('-z m', NS(foo_bar=None, zabbaz=None, qux=None, z='m')),
]
self.assertTypeError('-', errmsg='dest= is required')
self.assertTypeError('--', errmsg='dest= is required')
self.assertTypeError('---', errmsg='dest= is required')
+ self.assertTypeError('-', '--', '---',
+ errmsg="dest= is required for options like '-', '--', '---'")
def test_invalid_prefix(self):
self.assertValueError('--foo', '+foo',
--- /dev/null
+Fix inferring *dest* from a single-dash long option in :mod:`argparse`. If a
+short option and a single-dash long option are passed to
+:meth:`!add_argument`, *dest* is now inferred from the single-dash long
+option.