From 849009c7d396f81df3c18f7c1f3d421e699c39f3 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Fri, 24 Jul 2026 09:57:34 -0700 Subject: [PATCH 1/4] Use lazy imports in argparse --- Lib/argparse.py | 39 ++++++++++++++++----------------------- Lib/test/test_argparse.py | 8 +++++--- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 29e6ebb9634261a..6d396195f847655 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -87,12 +87,17 @@ import os as _os -import re as _re import sys as _sys -from gettext import gettext as _ -from gettext import ngettext lazy import _colorize +lazy import copy as _copy +lazy import difflib as _difflib +lazy import re as _re +lazy import shutil as _shutil +lazy import textwrap as _textwrap +lazy import warnings as _warnings +lazy from gettext import gettext as _ +lazy from gettext import ngettext SUPPRESS = '==SUPPRESS==' @@ -143,11 +148,9 @@ def _copy_items(items): return [] # The copy module is used only in the 'append' and 'append_const' # actions, and it is needed only when the default value isn't a list. - # Delay its import for speeding up the common case. if type(items) is list: return items[:] - import copy - return copy.copy(items) + return _copy.copy(items) def _identity(value): @@ -186,8 +189,7 @@ def __init__( ): # default setting for width if width is None: - import shutil - width = shutil.get_terminal_size().columns + width = _shutil.get_terminal_size().columns width -= 2 self._prog = prog @@ -773,15 +775,11 @@ def _iter_indented_subactions(self, action): def _split_lines(self, text, width): text = self._whitespace_matcher.sub(' ', text).strip() - # The textwrap module is used only for formatting help. - # Delay its import for speeding up the common usage of argparse. - import textwrap - return textwrap.wrap(text, width) + return _textwrap.wrap(text, width) def _fill_text(self, text, width, indent): text = self._whitespace_matcher.sub(' ', text).strip() - import textwrap - return textwrap.fill(text, width, + return _textwrap.fill(text, width, initial_indent=indent, subsequent_indent=indent) @@ -1458,8 +1456,7 @@ class FileType(object): """ def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None): - import warnings - warnings.warn( + _warnings.warn( "FileType is deprecated. Simply open files after parsing arguments.", category=PendingDeprecationWarning, stacklevel=2 @@ -1865,12 +1862,11 @@ class _ArgumentGroup(_ActionsContainer): def __init__(self, container, title=None, description=None, **kwargs): if 'prefix_chars' in kwargs: - import warnings depr_msg = ( "The use of the undocumented 'prefix_chars' parameter in " "ArgumentParser.add_argument_group() is deprecated." ) - warnings.warn(depr_msg, DeprecationWarning, stacklevel=3) + _warnings.warn(depr_msg, DeprecationWarning, stacklevel=3) # add any missing keyword arguments by checking the container update = kwargs.setdefault @@ -2796,8 +2792,7 @@ def _check_value(self, action, value): if self.suggest_on_error and isinstance(value, str): if all(isinstance(choice, str) for choice in action.choices): - import difflib - suggestions = difflib.get_close_matches(value, action.choices, 1) + suggestions = _difflib.get_close_matches(value, action.choices, 1) if suggestions: args['closest'] = suggestions[0] msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' @@ -2936,8 +2931,6 @@ def _warning(self, message): def __getattr__(name): if name == "__version__": - from warnings import _deprecated - - _deprecated("__version__", remove=(3, 20)) + _warnings._deprecated("__version__", remove=(3, 20)) return "1.1" # Do not change raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 1dc3f538f4ad8ba..eae66eadce4d08b 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -85,6 +85,8 @@ class TestLazyImports(unittest.TestCase): "_colorize", "copy", "difflib", + "gettext", + "re", "shutil", "textwrap", "warnings", @@ -99,7 +101,7 @@ def test_create_parser(self): # Test imports are still unused after # creating a parser create_parser = "argparse.ArgumentParser()" - imported_modules = {"shutil"} + imported_modules = {"gettext", "re", "shutil"} import_helper.ensure_lazy_imports( "argparse", @@ -114,7 +116,7 @@ def test_add_subparser(self): parser.add_subparsers(dest='command', required=False) """ ) - imported_modules = {"shutil"} + imported_modules = {"gettext", "re", "shutil"} import_helper.ensure_lazy_imports( "argparse", @@ -132,7 +134,7 @@ def test_parse_args(self): parser.parse_args(['BAR', '--foo', 'FOO']) """ ) - imported_modules = {"shutil"} + imported_modules = {"gettext", "re", "shutil"} import_helper.ensure_lazy_imports( "argparse", self.LAZY_IMPORTS - imported_modules, From c29e163f9facddf273345dc862902b8e21a5cca5 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:02:52 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-07-24-17-02-46.gh-issue-154638.7YnFHH.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-24-17-02-46.gh-issue-154638.7YnFHH.rst diff --git a/Misc/NEWS.d/next/Library/2026-07-24-17-02-46.gh-issue-154638.7YnFHH.rst b/Misc/NEWS.d/next/Library/2026-07-24-17-02-46.gh-issue-154638.7YnFHH.rst new file mode 100644 index 000000000000000..91c22640deba6b3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-24-17-02-46.gh-issue-154638.7YnFHH.rst @@ -0,0 +1 @@ +Improve import time of :mod:`argparse` by lazily importing several dependencies. From 974dc9913300fc16cb6aea2bda31b5e750a6d6fa Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Fri, 24 Jul 2026 12:12:04 -0700 Subject: [PATCH 3/4] Use un-underscored names --- Lib/argparse.py | 26 +++++++++++++------------- Lib/test/test_argparse.py | 6 +++++- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 6d396195f847655..720362e6674ab98 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -90,12 +90,12 @@ import sys as _sys lazy import _colorize -lazy import copy as _copy -lazy import difflib as _difflib +lazy import copy +lazy import difflib lazy import re as _re -lazy import shutil as _shutil -lazy import textwrap as _textwrap -lazy import warnings as _warnings +lazy import shutil +lazy import textwrap +lazy import warnings lazy from gettext import gettext as _ lazy from gettext import ngettext @@ -150,7 +150,7 @@ def _copy_items(items): # actions, and it is needed only when the default value isn't a list. if type(items) is list: return items[:] - return _copy.copy(items) + return copy.copy(items) def _identity(value): @@ -189,7 +189,7 @@ def __init__( ): # default setting for width if width is None: - width = _shutil.get_terminal_size().columns + width = shutil.get_terminal_size().columns width -= 2 self._prog = prog @@ -775,11 +775,11 @@ def _iter_indented_subactions(self, action): def _split_lines(self, text, width): text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.wrap(text, width) + return textwrap.wrap(text, width) def _fill_text(self, text, width, indent): text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.fill(text, width, + return textwrap.fill(text, width, initial_indent=indent, subsequent_indent=indent) @@ -1456,7 +1456,7 @@ class FileType(object): """ def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None): - _warnings.warn( + warnings.warn( "FileType is deprecated. Simply open files after parsing arguments.", category=PendingDeprecationWarning, stacklevel=2 @@ -1866,7 +1866,7 @@ def __init__(self, container, title=None, description=None, **kwargs): "The use of the undocumented 'prefix_chars' parameter in " "ArgumentParser.add_argument_group() is deprecated." ) - _warnings.warn(depr_msg, DeprecationWarning, stacklevel=3) + warnings.warn(depr_msg, DeprecationWarning, stacklevel=3) # add any missing keyword arguments by checking the container update = kwargs.setdefault @@ -2792,7 +2792,7 @@ def _check_value(self, action, value): if self.suggest_on_error and isinstance(value, str): if all(isinstance(choice, str) for choice in action.choices): - suggestions = _difflib.get_close_matches(value, action.choices, 1) + suggestions = difflib.get_close_matches(value, action.choices, 1) if suggestions: args['closest'] = suggestions[0] msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' @@ -2931,6 +2931,6 @@ def _warning(self, message): def __getattr__(name): if name == "__version__": - _warnings._deprecated("__version__", remove=(3, 20)) + warnings._deprecated("__version__", remove=(3, 20)) return "1.1" # Do not change raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index eae66eadce4d08b..5ca45f43de1e121 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -12,6 +12,7 @@ import sys import textwrap import tempfile +import types import unittest import argparse import warnings @@ -7100,7 +7101,10 @@ def test_all_exports_everything_but_modules(self): name for name, value in vars(argparse).items() if not (name.startswith("_") or name == 'ngettext') - if not inspect.ismodule(value) + if not ( + inspect.ismodule(value) + or isinstance(value, types.LazyImportType) + ) ] self.assertEqual(sorted(items), sorted(argparse.__all__)) From 11eb67411c3fd0967a06bb2d642ebc64aa8b487b Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Fri, 24 Jul 2026 12:12:50 -0700 Subject: [PATCH 4/4] Fix test --- Lib/test/test_argparse.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 5ca45f43de1e121..73798597b60d9b8 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -3,7 +3,6 @@ import _colorize import contextlib import functools -import inspect import io import operator import os @@ -7101,9 +7100,9 @@ def test_all_exports_everything_but_modules(self): name for name, value in vars(argparse).items() if not (name.startswith("_") or name == 'ngettext') - if not ( - inspect.ismodule(value) - or isinstance(value, types.LazyImportType) + if not isinstance( + value, + (types.ModuleType, types.LazyImportType), ) ] self.assertEqual(sorted(items), sorted(argparse.__all__))