Skip to content

gh-121450: Make inline breakpoints use the most recent pdb instance #121451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 11, 2024
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ pathlib
another.
(Contributed by Barney Gale in :gh:`73991`.)

pdb
---

* Hard-coded breakpoints (:func:`breakpoint` and :func:`pdb.set_trace()`) now
reuse the most recent :class:`~pdb.Pdb` instance that calls
:meth:`~pdb.Pdb.set_trace()`, instead of creating a new one each time.
As a result, all the instance specific data like :pdbcmd:`display` and
:pdbcmd:`commands` are preserved across hard-coded breakpoints.
(Contributed by Tian Gao in :gh:`121450`.)

symtable
--------

Expand Down
1 change: 1 addition & 0 deletions Lib/bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ def set_trace(self, frame=None):

If frame is not specified, debugging starts from caller's frame.
"""
sys.settrace(None)
if frame is None:
frame = sys._getframe().f_back
self.reset()
Expand Down
13 changes: 12 additions & 1 deletion Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):

_file_mtime_table = {}

_last_pdb_instance = None

def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
nosigint=False, readrc=True):
bdb.Bdb.__init__(self, skip=skip)
Expand Down Expand Up @@ -359,6 +361,12 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
self._chained_exceptions = tuple()
self._chained_exception_index = 0

def set_trace(self, frame=None):
Pdb._last_pdb_instance = self
if frame is None:
frame = sys._getframe().f_back
super().set_trace(frame)

def sigint_handler(self, signum, frame):
if self.allow_kbdint:
raise KeyboardInterrupt
Expand Down Expand Up @@ -2350,7 +2358,10 @@ def set_trace(*, header=None):
an assertion fails). If given, *header* is printed to the console
just before debugging begins.
"""
pdb = Pdb()
if Pdb._last_pdb_instance is not None:
pdb = Pdb._last_pdb_instance
else:
pdb = Pdb()
if header is not None:
pdb.message(header)
pdb.set_trace(sys._getframe().f_back)
Expand Down
43 changes: 43 additions & 0 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2448,6 +2448,49 @@ def test_pdb_show_attribute_and_item():
(Pdb) c
"""

# doctest will modify pdb.set_trace during the test, so we need to backup
# the original function to use it in the test
original_pdb_settrace = pdb.set_trace

def test_pdb_with_inline_breakpoint():
"""Hard-coded breakpoint() calls should invoke the same debugger instance

>>> def test_function():
... x = 1
... import pdb; pdb.Pdb().set_trace()
... original_pdb_settrace()
... x = 2

>>> with PdbTestInput(['display x',
... 'n',
... 'n',
... 'n',
... 'n',
... 'undisplay',
... 'c']):
... test_function()
> <doctest test.test_pdb.test_pdb_with_inline_breakpoint[0]>(3)test_function()
-> import pdb; pdb.Pdb().set_trace()
(Pdb) display x
display x: 1
(Pdb) n
> <doctest test.test_pdb.test_pdb_with_inline_breakpoint[0]>(4)test_function()
-> original_pdb_settrace()
(Pdb) n
> <doctest test.test_pdb.test_pdb_with_inline_breakpoint[0]>(4)test_function()
-> original_pdb_settrace()
(Pdb) n
> <doctest test.test_pdb.test_pdb_with_inline_breakpoint[0]>(5)test_function()
-> x = 2
(Pdb) n
--Return--
> <doctest test.test_pdb.test_pdb_with_inline_breakpoint[0]>(5)test_function()->None
-> x = 2
display x: 2 [old: 1]
(Pdb) undisplay
(Pdb) c
"""

def test_pdb_issue_20766():
"""Test for reference leaks when the SIGINT handler is set.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Hard-coded breakpoints (:func:`breakpoint` and :func:`pdb.set_trace()`) now
reuse the most recent ``Pdb`` instance that calls ``Pdb.set_trace()``,
instead of creating a new one each time. As a result, all the instance specific
data like ``display`` and ``commands`` are preserved across Hard-coded breakpoints.
Loading