Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-83151: Make closure work on pdb#111094

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
brandtbucher merged 13 commits intopython:mainfromgaogaotiantian:pdb-closure
May 6, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
13 commits
Select commitHold shift + click to select a range
720a584
Make closure work on pdb
gaogaotiantianOct 20, 2023
8232457
Always update local variables
gaogaotiantianOct 20, 2023
8df6d2b
📜🤖 Added by blurb_it.
blurb-it[bot]Oct 20, 2023
9c68aef
Merge branch 'main' into pdb-closure
gaogaotiantianMay 4, 2024
fe5a6f6
Update the code for pep 667
gaogaotiantianMay 5, 2024
cc21873
Remove blank line
gaogaotiantianMay 5, 2024
d37b5a7
Merge branch 'main' into pdb-closure
gaogaotiantianMay 5, 2024
cd46a05
Update 2023-10-20-03-50-17.gh-issue-83151.bcsD40.rst
gaogaotiantianMay 5, 2024
d04bc5d
Merge branch 'main' into pdb-closure
gaogaotiantianMay 5, 2024
aa91238
Fix test for breakpoint change
gaogaotiantianMay 5, 2024
7650aa9
Apply suggestions from code review
gaogaotiantianMay 6, 2024
3c44f88
Fix some typos in review and add exception check
gaogaotiantianMay 6, 2024
18e7a16
Apply suggestions from code review
gaogaotiantianMay 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletionLib/pdb.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,10 +77,12 @@
import code
import glob
import token
import types
import codeop
import pprint
import signal
import inspect
import textwrap
import tokenize
import traceback
import linecache
Expand DownExpand Up@@ -624,11 +626,96 @@ def _disable_command_completion(self):
self.completenames = completenames
return

def _exec_in_closure(self, source, globals, locals):
""" Run source code in closure so code object created within source
can find variables in locals correctly

returns True if the source is executed, False otherwise
"""

# Determine if the source should be executed in closure. Only when the
# source compiled to multiple code objects, we should use this feature.
# Otherwise, we can just raise an exception and normal exec will be used.

code = compile(source, "<string>", "exec")
if not any(isinstance(const, CodeType) for const in code.co_consts):
return False

# locals could be a proxy which does not support pop
# copy it first to avoid modifying the original locals
locals_copy = dict(locals)

locals_copy["__pdb_eval__"] = {
"result": None,
"write_back": {}
}

# If the source is an expression, we need to print its value
try:
compile(source, "<string>", "eval")
except SyntaxError:
pass
else:
source = "__pdb_eval__['result'] = " + source

# Add write-back to update the locals
source = ("try:\n" +
textwrap.indent(source, " ") + "\n" +
"finally:\n" +
" __pdb_eval__['write_back'] = locals()")

# Build a closure source code with freevars from locals like:
# def __pdb_outer():
# var = None
# def __pdb_scope(): # This is the code object we want to execute
# nonlocal var
# <source>
# return __pdb_scope.__code__
source_with_closure = ("def __pdb_outer():\n" +
"\n".join(f" {var} = None" for var in locals_copy) + "\n" +
" def __pdb_scope():\n" +
"\n".join(f" nonlocal {var}" for var in locals_copy) + "\n" +
textwrap.indent(source, " ") + "\n" +
" return __pdb_scope.__code__"
)

# Get the code object of __pdb_scope()
# The exec fills locals_copy with the __pdb_outer() function and we can call
# that to get the code object of __pdb_scope()
ns = {}
try:
exec(source_with_closure, {}, ns)
except Exception:
return False
code = ns["__pdb_outer"]()

cells = tuple(types.CellType(locals_copy.get(var)) for var in code.co_freevars)

try:
exec(code, globals, locals_copy, closure=cells)
except Exception:
return False

# get the data we need from the statement
pdb_eval = locals_copy["__pdb_eval__"]

# __pdb_eval__ should not be updated back to locals
pdb_eval["write_back"].pop("__pdb_eval__")

# Write all local variables back to locals
locals.update(pdb_eval["write_back"])
eval_result = pdb_eval["result"]
if eval_result is not None:
print(repr(eval_result))

return True

def default(self, line):
if line[:1] == '!': line = line[1:].strip()
locals = self.curframe_locals
globals = self.curframe.f_globals
try:
buffer = line
if (code := codeop.compile_command(line + '\n', '<stdin>', 'single')) is None:
# Multi-line mode
with self._disable_command_completion():
Expand DownExpand Up@@ -661,7 +748,8 @@ def default(self, line):
sys.stdin = self.stdin
sys.stdout = self.stdout
sys.displayhook = self.displayhook
exec(code, globals, locals)
if not self._exec_in_closure(buffer, globals, locals):
exec(code, globals, locals)
finally:
sys.stdout = save_stdout
sys.stdin = save_stdin
Expand Down
65 changes: 64 additions & 1 deletionLib/test/test_pdb.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2224,8 +2224,71 @@ def test_pdb_multiline_statement():
(Pdb) c
"""

def test_pdb_closure():
"""Test for all expressions/statements that involve closure

>>> k = 0
>>> g = 1
>>> def test_function():
... x = 2
... g = 3
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()

>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
... 'k',
... 'g',
... 'y = y',
... 'global g; g',
... 'global g; (lambda: g)()',
... '(lambda: x)()',
... '(lambda: g)()',
... 'lst = [n for n in range(10) if (n % x) == 0]',
... 'lst',
... 'sum(n for n in lst if n > x)',
... 'x = 1; raise Exception()',
... 'x',
... 'def f():',
... ' return x',
... '',
... 'f()',
... 'c'
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_closure[2]>(4)test_function()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) k
0
(Pdb) g
3
(Pdb) y = y
*** NameError: name 'y' is not defined
(Pdb) global g; g
1
(Pdb) global g; (lambda: g)()
1
(Pdb) (lambda: x)()
2
(Pdb) (lambda: g)()
3
(Pdb) lst = [n for n in range(10) if (n % x) == 0]
(Pdb) lst
[0, 2, 4, 6, 8]
(Pdb) sum(n for n in lst if n > x)
18
(Pdb) x = 1; raise Exception()
*** Exception
(Pdb) x
1
(Pdb) def f():
... return x
...
(Pdb) f()
1
(Pdb) c
"""

def test_pdb_show_attribute_and_item():
"""Test formultiline statement
"""Test forexpressions with command prefix

>>> def test_function():
... n = lambda x: x
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
Enabled arbitrary statements and evaluations in :mod:`pdb` shell to access the
local variables of the current frame, which made it possible for multi-scope
code like generators or nested function to work.

[8]ページ先頭

©2009-2025 Movatter.jp