https://github.com/python/cpython/commit/425fd85ca360a39a1a3fb16f09c448cb93ff794a
commit: 425fd85ca360a39a1a3fb16f09c448cb93ff794a
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-11-22T20:54:02Z
summary:
gh-138525: Support single-dash long options and prefix_chars in
BooleanOptionalAction (GH-138692)
-nofoo is generated for -foo.
++no-foo is generated for ++foo.
/nofoo is generated for /foo.
files:
A Misc/NEWS.d/next/Library/2025-09-09-10-13-24.gh-issue-138525.hDTaAM.rst
M Doc/library/argparse.rst
M Doc/whatsnew/3.15.rst
M Lib/argparse.py
M Lib/test/test_argparse.py
diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index 30ddd849f3a2ef..2a39f248651936 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -1445,8 +1445,18 @@ this API may be passed as the ``action`` parameter to
>>> parser.parse_args(['--no-foo'])
Namespace(foo=False)
+ Single-dash long options are also supported.
+ For example, negative option ``-nofoo`` is automatically added for
+ positive option ``-foo``.
+ But no additional options are added for short options such as ``-f``.
+
.. versionadded:: 3.9
+ .. versionchanged:: next
+ Added support for single-dash options.
+
+ Added support for alternate prefix_chars_.
+
The parse_args() method
-----------------------
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index 8991584a9f22dd..4882ddb4310fc2 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -416,6 +416,10 @@ Improved modules
argparse
--------
+* The :class:`~argparse.BooleanOptionalAction` action supports now single-dash
+ long options and alternate prefix characters.
+ (Contributed by Serhiy Storchaka in :gh:`138525`.)
+
* Changed the *suggest_on_error* parameter of :class:`argparse.ArgumentParser`
to
default to ``True``. This enables suggestions for mistyped arguments by
default.
(Contributed by Jakob Schluse in :gh:`140450`.)
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 5003927cb30485..02a17d93bdfcf5 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -932,15 +932,26 @@ def __init__(self,
deprecated=False):
_option_strings = []
+ neg_option_strings = []
for option_string in option_strings:
_option_strings.append(option_string)
- if option_string.startswith('--'):
- if option_string.startswith('--no-'):
+ if len(option_string) > 2 and option_string[0] == option_string[1]:
+ # two-dash long option: '--foo' -> '--no-foo'
+ if option_string.startswith('no-', 2):
raise ValueError(f'invalid option name {option_string!r} '
f'for BooleanOptionalAction')
- option_string = '--no-' + option_string[2:]
+ option_string = option_string[:2] + 'no-' + option_string[2:]
_option_strings.append(option_string)
+ neg_option_strings.append(option_string)
+ elif len(option_string) > 2 and option_string[0] !=
option_string[1]:
+ # single-dash long option: '-foo' -> '-nofoo'
+ if option_string.startswith('no', 1):
+ raise ValueError(f'invalid option name {option_string!r} '
+ f'for BooleanOptionalAction')
+ option_string = option_string[:1] + 'no' + option_string[1:]
+ _option_strings.append(option_string)
+ neg_option_strings.append(option_string)
super().__init__(
option_strings=_option_strings,
@@ -950,11 +961,12 @@ def __init__(self,
required=required,
help=help,
deprecated=deprecated)
+ self.neg_option_strings = neg_option_strings
def __call__(self, parser, namespace, values, option_string=None):
if option_string in self.option_strings:
- setattr(namespace, self.dest, not
option_string.startswith('--no-'))
+ setattr(namespace, self.dest, option_string not in
self.neg_option_strings)
def format_usage(self):
return ' | '.join(self.option_strings)
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index b93502a74596df..8af51b1fc6eb26 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -805,6 +805,76 @@ def test_invalid_name(self):
self.assertEqual(str(cm.exception),
"invalid option name '--no-foo' for
BooleanOptionalAction")
+class TestBooleanOptionalActionSingleDash(ParserTestCase):
+ """Tests BooleanOptionalAction with single dash"""
+
+ argument_signatures = [
+ Sig('-foo', '-x', action=argparse.BooleanOptionalAction),
+ ]
+ failures = ['--foo', '--no-foo', '-no-foo', '-no-x', '-nox']
+ successes = [
+ ('', NS(foo=None)),
+ ('-foo', NS(foo=True)),
+ ('-nofoo', NS(foo=False)),
+ ('-x', NS(foo=True)),
+ ]
+
+ def test_invalid_name(self):
+ parser = argparse.ArgumentParser()
+ with self.assertRaises(ValueError) as cm:
+ parser.add_argument('-nofoo',
action=argparse.BooleanOptionalAction)
+ self.assertEqual(str(cm.exception),
+ "invalid option name '-nofoo' for
BooleanOptionalAction")
+
+class TestBooleanOptionalActionAlternatePrefixChars(ParserTestCase):
+ """Tests BooleanOptionalAction with custom prefixes"""
+
+ parser_signature = Sig(prefix_chars='+-', add_help=False)
+ argument_signatures = [Sig('++foo', action=argparse.BooleanOptionalAction)]
+ failures = ['--foo', '--no-foo']
+ successes = [
+ ('', NS(foo=None)),
+ ('++foo', NS(foo=True)),
+ ('++no-foo', NS(foo=False)),
+ ]
+
+ def test_invalid_name(self):
+ parser = argparse.ArgumentParser(prefix_chars='+/')
+ with self.assertRaisesRegex(ValueError,
+ 'BooleanOptionalAction.*is not valid for positional
arguments'):
+ parser.add_argument('--foo', action=argparse.BooleanOptionalAction)
+ with self.assertRaises(ValueError) as cm:
+ parser.add_argument('++no-foo',
action=argparse.BooleanOptionalAction)
+ self.assertEqual(str(cm.exception),
+ "invalid option name '++no-foo' for
BooleanOptionalAction")
+
+class TestBooleanOptionalActionSingleAlternatePrefixChar(ParserTestCase):
+ """Tests BooleanOptionalAction with single alternate prefix char"""
+
+ parser_signature = Sig(prefix_chars='+/', add_help=False)
+ argument_signatures = [
+ Sig('+foo', '+x', action=argparse.BooleanOptionalAction),
+ ]
+ failures = ['++foo', '++no-foo', '++nofoo',
+ '-no-foo', '-nofoo', '+no-foo', '-nofoo',
+ '+no-x', '+nox', '-no-x', '-nox']
+ successes = [
+ ('', NS(foo=None)),
+ ('+foo', NS(foo=True)),
+ ('+nofoo', NS(foo=False)),
+ ('+x', NS(foo=True)),
+ ]
+
+ def test_invalid_name(self):
+ parser = argparse.ArgumentParser(prefix_chars='+/')
+ with self.assertRaisesRegex(ValueError,
+ 'BooleanOptionalAction.*is not valid for positional
arguments'):
+ parser.add_argument('-foo', action=argparse.BooleanOptionalAction)
+ with self.assertRaises(ValueError) as cm:
+ parser.add_argument('+nofoo',
action=argparse.BooleanOptionalAction)
+ self.assertEqual(str(cm.exception),
+ "invalid option name '+nofoo' for
BooleanOptionalAction")
+
class TestBooleanOptionalActionRequired(ParserTestCase):
"""Tests BooleanOptionalAction required"""
diff --git
a/Misc/NEWS.d/next/Library/2025-09-09-10-13-24.gh-issue-138525.hDTaAM.rst
b/Misc/NEWS.d/next/Library/2025-09-09-10-13-24.gh-issue-138525.hDTaAM.rst
new file mode 100644
index 00000000000000..c4cea4b74b8a4c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-09-09-10-13-24.gh-issue-138525.hDTaAM.rst
@@ -0,0 +1,2 @@
+Add support for single-dash long options and alternate prefix characters in
+:class:`argparse.BooleanOptionalAction`.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]