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

Commite5353d4

Browse files
GH-83151: Add closure support to pdb (GH-111094)
1 parent5a1618a commite5353d4

File tree

3 files changed

+156
-2
lines changed

3 files changed

+156
-2
lines changed

‎Lib/pdb.py‎

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,12 @@
7777
importcode
7878
importglob
7979
importtoken
80+
importtypes
8081
importcodeop
8182
importpprint
8283
importsignal
8384
importinspect
85+
importtextwrap
8486
importtokenize
8587
importtraceback
8688
importlinecache
@@ -624,11 +626,96 @@ def _disable_command_completion(self):
624626
self.completenames=completenames
625627
return
626628

629+
def_exec_in_closure(self,source,globals,locals):
630+
""" Run source code in closure so code object created within source
631+
can find variables in locals correctly
632+
633+
returns True if the source is executed, False otherwise
634+
"""
635+
636+
# Determine if the source should be executed in closure. Only when the
637+
# source compiled to multiple code objects, we should use this feature.
638+
# Otherwise, we can just raise an exception and normal exec will be used.
639+
640+
code=compile(source,"<string>","exec")
641+
ifnotany(isinstance(const,CodeType)forconstincode.co_consts):
642+
returnFalse
643+
644+
# locals could be a proxy which does not support pop
645+
# copy it first to avoid modifying the original locals
646+
locals_copy=dict(locals)
647+
648+
locals_copy["__pdb_eval__"]= {
649+
"result":None,
650+
"write_back": {}
651+
}
652+
653+
# If the source is an expression, we need to print its value
654+
try:
655+
compile(source,"<string>","eval")
656+
exceptSyntaxError:
657+
pass
658+
else:
659+
source="__pdb_eval__['result'] = "+source
660+
661+
# Add write-back to update the locals
662+
source= ("try:\n"+
663+
textwrap.indent(source," ")+"\n"+
664+
"finally:\n"+
665+
" __pdb_eval__['write_back'] = locals()")
666+
667+
# Build a closure source code with freevars from locals like:
668+
# def __pdb_outer():
669+
# var = None
670+
# def __pdb_scope(): # This is the code object we want to execute
671+
# nonlocal var
672+
# <source>
673+
# return __pdb_scope.__code__
674+
source_with_closure= ("def __pdb_outer():\n"+
675+
"\n".join(f"{var} = None"forvarinlocals_copy)+"\n"+
676+
" def __pdb_scope():\n"+
677+
"\n".join(f" nonlocal{var}"forvarinlocals_copy)+"\n"+
678+
textwrap.indent(source," ")+"\n"+
679+
" return __pdb_scope.__code__"
680+
)
681+
682+
# Get the code object of __pdb_scope()
683+
# The exec fills locals_copy with the __pdb_outer() function and we can call
684+
# that to get the code object of __pdb_scope()
685+
ns= {}
686+
try:
687+
exec(source_with_closure, {},ns)
688+
exceptException:
689+
returnFalse
690+
code=ns["__pdb_outer"]()
691+
692+
cells=tuple(types.CellType(locals_copy.get(var))forvarincode.co_freevars)
693+
694+
try:
695+
exec(code,globals,locals_copy,closure=cells)
696+
exceptException:
697+
returnFalse
698+
699+
# get the data we need from the statement
700+
pdb_eval=locals_copy["__pdb_eval__"]
701+
702+
# __pdb_eval__ should not be updated back to locals
703+
pdb_eval["write_back"].pop("__pdb_eval__")
704+
705+
# Write all local variables back to locals
706+
locals.update(pdb_eval["write_back"])
707+
eval_result=pdb_eval["result"]
708+
ifeval_resultisnotNone:
709+
print(repr(eval_result))
710+
711+
returnTrue
712+
627713
defdefault(self,line):
628714
ifline[:1]=='!':line=line[1:].strip()
629715
locals=self.curframe_locals
630716
globals=self.curframe.f_globals
631717
try:
718+
buffer=line
632719
if (code:=codeop.compile_command(line+'\n','<stdin>','single'))isNone:
633720
# Multi-line mode
634721
withself._disable_command_completion():
@@ -661,7 +748,8 @@ def default(self, line):
661748
sys.stdin=self.stdin
662749
sys.stdout=self.stdout
663750
sys.displayhook=self.displayhook
664-
exec(code,globals,locals)
751+
ifnotself._exec_in_closure(buffer,globals,locals):
752+
exec(code,globals,locals)
665753
finally:
666754
sys.stdout=save_stdout
667755
sys.stdin=save_stdin

‎Lib/test/test_pdb.py‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2224,8 +2224,71 @@ def test_pdb_multiline_statement():
22242224
(Pdb) c
22252225
"""
22262226

2227+
deftest_pdb_closure():
2228+
"""Test for all expressions/statements that involve closure
2229+
2230+
>>> k = 0
2231+
>>> g = 1
2232+
>>> def test_function():
2233+
... x = 2
2234+
... g = 3
2235+
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
2236+
2237+
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
2238+
... 'k',
2239+
... 'g',
2240+
... 'y = y',
2241+
... 'global g; g',
2242+
... 'global g; (lambda: g)()',
2243+
... '(lambda: x)()',
2244+
... '(lambda: g)()',
2245+
... 'lst = [n for n in range(10) if (n % x) == 0]',
2246+
... 'lst',
2247+
... 'sum(n for n in lst if n > x)',
2248+
... 'x = 1; raise Exception()',
2249+
... 'x',
2250+
... 'def f():',
2251+
... ' return x',
2252+
... '',
2253+
... 'f()',
2254+
... 'c'
2255+
... ]):
2256+
... test_function()
2257+
> <doctest test.test_pdb.test_pdb_closure[2]>(4)test_function()
2258+
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
2259+
(Pdb) k
2260+
0
2261+
(Pdb) g
2262+
3
2263+
(Pdb) y = y
2264+
*** NameError: name 'y' is not defined
2265+
(Pdb) global g; g
2266+
1
2267+
(Pdb) global g; (lambda: g)()
2268+
1
2269+
(Pdb) (lambda: x)()
2270+
2
2271+
(Pdb) (lambda: g)()
2272+
3
2273+
(Pdb) lst = [n for n in range(10) if (n % x) == 0]
2274+
(Pdb) lst
2275+
[0, 2, 4, 6, 8]
2276+
(Pdb) sum(n for n in lst if n > x)
2277+
18
2278+
(Pdb) x = 1; raise Exception()
2279+
*** Exception
2280+
(Pdb) x
2281+
1
2282+
(Pdb) def f():
2283+
... return x
2284+
...
2285+
(Pdb) f()
2286+
1
2287+
(Pdb) c
2288+
"""
2289+
22272290
deftest_pdb_show_attribute_and_item():
2228-
"""Test formultiline statement
2291+
"""Test forexpressions with command prefix
22292292
22302293
>>> def test_function():
22312294
... n = lambda x: x
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Enabled arbitrary statements and evaluations in:mod:`pdb` shell to access the
2+
local variables of the current frame, which made it possible for multi-scope
3+
code like generators or nested function to work.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp