From bc55687066363a6a4ec24177cfe9c09758b7c5ad Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:27:18 +0300 Subject: [PATCH] Use lazy imports in pdb, bdb, code --- Lib/bdb.py | 8 +-- Lib/code.py | 2 +- Lib/pdb.py | 54 +++++++++---------- Lib/test/test_bdb.py | 5 ++ Lib/test/test_code_module.py | 5 ++ Lib/test/test_pdb.py | 28 +++++++++- ...-08-29-13-41-56.gh-issue-154675.cYvE-Z.rst | 2 + 7 files changed, 70 insertions(+), 34 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-29-13-41-56.gh-issue-154675.cYvE-Z.rst diff --git a/Lib/bdb.py b/Lib/bdb.py index 50cf2b3f5b3e453..431f545321a750e 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -1,12 +1,12 @@ """Debugger basics""" -import fnmatch -import sys -import threading import os -import weakref +import sys from contextlib import contextmanager from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR +lazy import fnmatch +lazy import threading +lazy import weakref __all__ = ["BdbQuit", "Bdb", "Breakpoint"] diff --git a/Lib/code.py b/Lib/code.py index df1d7199e339341..0d77e15defda435 100644 --- a/Lib/code.py +++ b/Lib/code.py @@ -7,8 +7,8 @@ import builtins import sys -import traceback from codeop import CommandCompiler, compile_command +lazy import traceback __all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact", "compile_command"] diff --git a/Lib/pdb.py b/Lib/pdb.py index 458eb8352652366..c0a043f13426646 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -67,45 +67,44 @@ import os import io -import re import sys import cmd -import bdb import dis +import bdb import code -import glob -import json import stat import token import types import atexit import codeop -import pprint -import signal -import socket import typing -import asyncio import inspect -import weakref import builtins -import tempfile -import textwrap -import tokenize import itertools -import traceback import linecache -import selectors -import threading -import _colorize from contextlib import ExitStack, closing, contextmanager from types import CodeType from warnings import deprecated -try: - import _pyrepl.utils -except ModuleNotFoundError: - _pyrepl = None +lazy import _colorize +lazy import argparse +lazy import asyncio +lazy import glob +lazy import json +lazy import pprint +lazy import re +lazy import runpy +lazy import selectors +lazy import shlex +lazy import signal +lazy import socket +lazy import tempfile +lazy import textwrap +lazy import threading +lazy import tokenize +lazy import traceback +lazy import weakref class Restart(Exception): @@ -246,7 +245,6 @@ class _ModuleTarget(_ExecutableTarget): def __init__(self, target): self._target = target - import runpy try: _, self._spec, self._code = runpy._get_module_details(self._target) except ImportError as e: @@ -281,8 +279,6 @@ def namespace(self): class _ZipTarget(_ExecutableTarget): def __init__(self, target): - import runpy - self._target = os.path.realpath(target) sys.path.insert(0, self._target) try: @@ -465,6 +461,7 @@ def complete(self, text, state): return None def gen_colors(self, buffer): + import _pyrepl.utils from _pyrepl.utils import ColorSpan, Span if not buffer.strip(): @@ -1266,7 +1263,11 @@ def handle_command_def(self, line): return False def _colorize_code(self, code): - if self.colorize and _pyrepl: + if self.colorize: + try: + import _pyrepl.utils + except ModuleNotFoundError: + return code colors = list(_pyrepl.utils.gen_colors(code)) chars, _ = _pyrepl.utils.disp_str(code, colors=colors, force_color=True) code = "".join(chars) @@ -2084,7 +2085,6 @@ def do_run(self, arg): 'e.g. "python -m pdb myscript.py"') return if arg: - import shlex argv0 = sys.argv[0:1] try: sys.argv = shlex.split(arg) @@ -3778,9 +3778,7 @@ def parse_args(): # "python -m pdb -m foo -p 1" should pass "-p 1" to "foo". # "python -m pdb foo.py -m bar" should pass "-m bar" to "foo.py". # "python -m pdb -m foo -m bar" should pass "-m bar" to "foo". - # This require some customized parsing logic to find the actual debug target. - - import argparse + # This requires some customized parsing logic to find the actual debug target. parser = argparse.ArgumentParser( usage="%(prog)s [-h] [-c command] (-m module | -p pid | pyfile) [args ...]", diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py index f15dae13eb384ef..bf2b8e1bb3eb1cf 100644 --- a/Lib/test/test_bdb.py +++ b/Lib/test/test_bdb.py @@ -57,6 +57,7 @@ import linecache from contextlib import contextmanager from itertools import islice, repeat +from test.support import cpython_only from test.support import import_helper from test.support import os_helper from test.support import patch_list @@ -1232,6 +1233,10 @@ def test_next_to_botframe(self): class TestRegressions(unittest.TestCase): + @cpython_only + def test_lazy_import(self): + import_helper.ensure_lazy_imports("bdb", {"fnmatch", "threading", "weakref"}) + def test_format_stack_entry_no_lineno(self): # See gh-101517 self.assertIn('Warning: lineno is None', diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py index 3642b47c2c1f03f..6651a3bf7f095ec 100644 --- a/Lib/test/test_code_module.py +++ b/Lib/test/test_code_module.py @@ -5,6 +5,7 @@ from textwrap import dedent from contextlib import ExitStack from unittest import mock +from test.support import cpython_only from test.support import force_not_colorized_test_class from test.support import import_helper @@ -38,6 +39,10 @@ def setUp(self): self.console = code.InteractiveConsole() self.mock_sys() + @cpython_only + def test_lazy_import(self): + import_helper.ensure_lazy_imports("code", {"traceback"}) + def test_ps1(self): self.infunc.side_effect = [ "import code", diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index e7b66031a86a6ca..5a2c1670e5af7f8 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -22,7 +22,7 @@ from io import StringIO from test import support from test.support import has_socket_support, os_helper -from test.support.import_helper import import_module +from test.support.import_helper import ensure_lazy_imports, import_module from test.support.pty_helper import run_pty, FakeInput from test.support.script_helper import kill_python from unittest.mock import patch @@ -3529,6 +3529,32 @@ class PdbTestCase(unittest.TestCase): def tearDown(self): os_helper.unlink(os_helper.TESTFN) + @support.cpython_only + def test_lazy_import(self): + ensure_lazy_imports( + "pdb", + { + "_colorize", + "argparse", + "asyncio", + "glob", + "json", + "pprint", + "re", + "runpy", + "selectors", + "shlex", + "signal", + "socket", + "tempfile", + "textwrap", + "threading", + "tokenize", + "traceback", + "weakref", + }, + ) + @unittest.skipIf(sys.flags.safe_path, 'PYTHONSAFEPATH changes default sys.path') def _run_pdb(self, pdb_args, commands, diff --git a/Misc/NEWS.d/next/Library/2026-08-29-13-41-56.gh-issue-154675.cYvE-Z.rst b/Misc/NEWS.d/next/Library/2026-08-29-13-41-56.gh-issue-154675.cYvE-Z.rst new file mode 100644 index 000000000000000..02d4996702b4584 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-29-13-41-56.gh-issue-154675.cYvE-Z.rst @@ -0,0 +1,2 @@ +Improve import time of :mod:`pdb`, :mod:`bdb` and :mod:`code` by lazily +importing several dependencies.