From 3c199f4ab5f4225e690e8ecfdd5cc581e649caca Mon Sep 17 00:00:00 2001 From: Brittany Reynoso Date: Mon, 31 Aug 2026 20:39:32 -0700 Subject: [PATCH 1/3] gh-999998: Speed up pdb startup with lazy imports Defer the eight imports that are not already pulled in by pdb's required-eager imports (cmd, bdb, code, typing and the guarded _pyrepl.utils), and lift five unguarded function-level imports to module-scope lazy imports. Guard Pdb._get_asyncio_task() on 'asyncio' in sys.modules. It runs on every interaction(), so without the guard the deferred asyncio import is paid back immediately on the first breakpoint and the lazy import buys nothing for an actual debugging session. If asyncio was never imported there cannot be a running task, so returning None is equivalent. The guarded imports (readline, rlcompleter, _pyrepl) stay function-level: they sit in try/except ImportError blocks, and hoisting them would move the failure outside the guard. import pdb -42.1% breakpoint() -> cont -41.1% python -m pdb script -33.4% pdb post-mortem -33.3% --- Lib/pdb.py | 31 ++++++++++--------- ...-08-31-11-40-00.gh-issue-999998.Pd8Lz1.rst | 2 ++ 2 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-999998.Pd8Lz1.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 458eb8352652366..f3cf827400a84f0 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -73,28 +73,32 @@ import bdb import dis import code -import glob -import json +lazy import glob +lazy import json import stat +lazy import runpy +lazy import shlex +lazy import pydoc import token import types import atexit import codeop -import pprint -import signal -import socket +lazy import pprint +lazy import signal +lazy import socket import typing -import asyncio +lazy import asyncio import inspect import weakref +lazy import argparse import builtins -import tempfile +lazy import tempfile import textwrap import tokenize import itertools import traceback import linecache -import selectors +lazy import selectors import threading import _colorize @@ -246,7 +250,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 +284,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: @@ -902,6 +903,10 @@ def _hold_exceptions(self, exceptions): self._chained_exception_index = 0 def _get_asyncio_task(self): + # If asyncio has never been imported there cannot be a running task, + # so skip the import rather than pay for it on every interaction. + if 'asyncio' not in sys.modules: + return None try: task = asyncio.current_task() except RuntimeError: @@ -2084,7 +2089,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) @@ -3739,7 +3743,6 @@ def test(): # print help def help(): - import pydoc pydoc.pager(__doc__) _usage = """\ @@ -3780,8 +3783,6 @@ def parse_args(): # "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 - parser = argparse.ArgumentParser( usage="%(prog)s [-h] [-c command] (-m module | -p pid | pyfile) [args ...]", description=_usage, diff --git a/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-999998.Pd8Lz1.rst b/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-999998.Pd8Lz1.rst new file mode 100644 index 000000000000000..89a7b550057ed90 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-999998.Pd8Lz1.rst @@ -0,0 +1,2 @@ +Speed up :mod:`pdb` startup by about 40% using :keyword:`lazy import`, and by +skipping the :mod:`asyncio` import when no event loop has been used. From 8d15fb11a7810d83b29612f357bfb0a48603ef20 Mon Sep 17 00:00:00 2001 From: Brittany Reynoso Date: Tue, 1 Sep 2026 06:53:50 -0700 Subject: [PATCH 2/3] gh-156774: Point pdb lazy-import NEWS entry at the filed issue --- ....Pd8Lz1.rst => 2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2026-08-31-11-40-00.gh-issue-999998.Pd8Lz1.rst => 2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-999998.Pd8Lz1.rst b/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-999998.Pd8Lz1.rst rename to Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst From 89fccc3c5ec5047a0a9f7d1aa16883cb7958c5ff Mon Sep 17 00:00:00 2001 From: Brittany Reynoso Date: Tue, 1 Sep 2026 08:36:42 -0700 Subject: [PATCH 3/3] gh-156774: Scope pdb change down to the asyncio import only Drop the other lazy imports and the lifted function-level imports, leaving just the asyncio deferral and the _get_asyncio_task() guard. Those two carry most of the win and keep the diff to five lines. import pdb -28.8% (was -42.1% with the full set) breakpoint() -> cont -28.5% python -m pdb script -26.0% pdb post-mortem -24.6% --- Lib/pdb.py | 25 +++++++++++-------- ...-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst | 5 ++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index f3cf827400a84f0..130e05bc3b6af22 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -73,32 +73,28 @@ import bdb import dis import code -lazy import glob -lazy import json +import glob +import json import stat -lazy import runpy -lazy import shlex -lazy import pydoc import token import types import atexit import codeop -lazy import pprint -lazy import signal -lazy import socket +import pprint +import signal +import socket import typing lazy import asyncio import inspect import weakref -lazy import argparse import builtins -lazy import tempfile +import tempfile import textwrap import tokenize import itertools import traceback import linecache -lazy import selectors +import selectors import threading import _colorize @@ -250,6 +246,7 @@ 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: @@ -284,6 +281,8 @@ 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: @@ -2089,6 +2088,7 @@ 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) @@ -3743,6 +3743,7 @@ def test(): # print help def help(): + import pydoc pydoc.pager(__doc__) _usage = """\ @@ -3783,6 +3784,8 @@ def parse_args(): # "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 + parser = argparse.ArgumentParser( usage="%(prog)s [-h] [-c command] (-m module | -p pid | pyfile) [args ...]", description=_usage, diff --git a/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst b/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst index 89a7b550057ed90..85104694a6b2efb 100644 --- a/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst +++ b/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst @@ -1,2 +1,3 @@ -Speed up :mod:`pdb` startup by about 40% using :keyword:`lazy import`, and by -skipping the :mod:`asyncio` import when no event loop has been used. +Speed up :mod:`pdb` startup by deferring the :mod:`asyncio` import, and by +skipping it entirely in ``Pdb._get_asyncio_task()`` when no event loop has +been used.