diff --git a/Lib/pdb.py b/Lib/pdb.py index 1506e3d4709817..76bb5edc232b05 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1337,7 +1337,7 @@ def do_commands(self, arg): complete_commands = _complete_bpnumber def do_break(self, arg, temporary=False): - """b(reak) [ ([filename:]lineno | function) [, condition] ] + """b(reak) [ [filename:](lineno | function) [, condition] ] Without argument, list all breaks. @@ -1347,9 +1347,9 @@ def do_break(self, arg, temporary=False): present, it is a string specifying an expression which must evaluate to true before the breakpoint is honored. - The line number may be prefixed with a filename and a colon, - to specify a breakpoint in another file (probably one that - hasn't been loaded yet). The file is searched for on + The line number and function may be prefixed with a filename and + a colon, to specify a breakpoint in another file (probably one + that hasn't been loaded yet). The file is searched for on sys.path; the .py suffix may be omitted. """ if not arg: @@ -1388,8 +1388,12 @@ def do_break(self, arg, temporary=False): try: lineno = int(arg) except ValueError: - self.error('Bad lineno: %s' % arg) - return + func = arg + find_res = find_function(func, self.canonic(filename)) + if not find_res: + self.error('Bad lineno or function name: %s' % arg) + return + funcname, filename, lineno = find_res else: # no colon; can be lineno or function try: @@ -1650,12 +1654,13 @@ def _prompt_for_confirmation(self, prompt, default): return reply.strip().lower() def do_clear(self, arg): - """cl(ear) [filename:lineno | bpnumber ...] + """cl(ear) [filename:(lineno | function) | bpnumber ...] With a space separated list of breakpoint numbers, clear those breakpoints. Without argument, clear all breaks (but first ask confirmation). With a filename:lineno argument, - clear all breaks at that line in that file. + clear all breakpoints at that line. With a filename:function + argument, clear all breakpoints at that function. """ if not arg: reply = self._prompt_for_confirmation( @@ -1671,13 +1676,24 @@ def do_clear(self, arg): if ':' in arg: # Make sure it works for "clear C:\foo\bar.py:12" i = arg.rfind(':') - filename = arg[:i] - arg = arg[i+1:] + filename = arg[:i].rstrip() + arg = arg[i+1:].lstrip() + err = None try: lineno = int(arg) except ValueError: - err = "Invalid line number (%s)" % arg - else: + f = self.lookupmodule(filename) + if not f: + err = '%r not found from sys.path' % filename + else: + filename = f + func = arg + find_res = find_function(func, self.canonic(filename)) + if find_res: + _, filename, lineno = find_res + else: + err = "Invalid line number or function name:(%s)" % arg + if not err: bplist = self.get_breaks(filename, lineno)[:] err = self.clear_break(filename, lineno) if err: diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index c097808e7fdc7c..b7568e739db56d 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -4622,6 +4622,20 @@ def foo(self): stdout, stderr = self.run_pdb_script(script, commands) self.assertIn("The specified object 'C.foo' is not a function", stdout) + def test_break_function_with_file(self): + script = """ + def foo(): + pass + """ + commands = """ + break main:foo + clear main:foo + quit + """ + stdout, stderr = self.run_pdb_script(script, commands) + self.assertRegex(stdout, r"Breakpoint 1 at .*main\.py:3") + self.assertRegex(stdout, r"Deleted breakpoint 1 at .*main\.py:3") + class ChecklineTests(unittest.TestCase): def setUp(self): diff --git a/Misc/NEWS.d/next/Library/2025-12-09-15-18-43.gh-issue-142468.V64wcC.rst b/Misc/NEWS.d/next/Library/2025-12-09-15-18-43.gh-issue-142468.V64wcC.rst new file mode 100644 index 00000000000000..2ab64b377841dc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-09-15-18-43.gh-issue-142468.V64wcC.rst @@ -0,0 +1 @@ +:mod:`pdb` now supports setting or clearing breakpoints with the ``filename:function`` syntax.