Repository navigation
pdb fails to access variables closed over #70260
Description
Activity
Consider the following example:
def f(x=1): def g(): y = 2 raise Exception g() f()
$ python -mpdb -ccontinue example.py <... traceback ...> > /tmp/example.py(4)g() -> raise Exception (Pdb) p x ##### this can be worked around using "up" *** NameError: name 'x' is not defined (Pdb) p y 2 (Pdb) p (lambda: y)() ##### this is more awkward to work around, e.g. (lambda *, y=y: y)() *** NameError: name 'y' is not defined
Use case: I wan to pass a lambda to a numerical optimizer, but the optimizer fails using the default starting point. Within pdb, I'd like to pass a different starting point but the same lambda, to see whether this helps.
- addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Jan 10, 2016 - addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Jan 15, 2016 Confirming.
Another use case is the use any lambda, or function definition inside the scope of a function, for checking conditions in the REPL.
Suppose two inner functions named condition1 and condition2, and a parameter X as a Collection:
(Pdb) any(condition1(x) and condition2(x) for x in X)
*** NameError: name 'condition1' is not definedWhat Antony Lee mentioned are two different cases.
The former is what PDB should behave because it is not reasonable to inspect a variable does not exist in the current frame. If you want to do so, you have to reference the variable
xas a closure inside inner functiongin your source code before running PDB.The latter is same as what Jesús Gómez confirmed. It's a problem about creating correct closures in an interaction prompt of PDB. It can be found in almost every versions of Python. The following are several forms of the problem:
# (1) raise exception
1 def f():
2 x = 2
3 -> import pdb; pdb.set_trace();
(Pdb) (lambda: x)()
*** NameError: name 'x' is not defined# (2) no exception, but get the wrong value
1 x = 100
2 def f():
3 x = 2
4 -> import pdb; pdb.set_trace();
5 f()
(Pdb) x
2
(Pdb) (lambda: x)()
100# (3) similar to (1), but this one usually makes me upset if I forget the cause of the problem
(Pdb) ll
1 def f():
2 -> import pdb; pdb.set_trace();
(Pdb) x = 5
(Pdb) [i for i in range(10) if (i % x) == 0]
*** NameError: name 'x' is not defined
(Pdb) x
5It's easy to alleviate the problem by using
interactcommand to start an interactive interpreter.1 def f(): 2 -> import pdb; pdb.set_trace(); (Pdb) x = 5 (Pdb) interact *interactive* >>> [i for i in range(10) if (i % x) == 0] [0, 5]
Now the behavior looks right.
However, the problem still exists for those PDB users don't know how to pass it, and it's quite inconvenient to start an interactive interpreter every time. Maybe it's worth to keep the behavior of PDB consistent and expectable just like the behavior inside interactive interpreter.
I will try to send a patch to solve the problem in these days. Please stop me if it's inappropriate.
Surrender.
After digging into the implementation of how CPython handles closures, I am sure that it is impossible to solve this problem now correctly. What I can do is create a patch to let Pdb print enough information when the problem occurs. It would look like:
(Pdb) ll
3 def f():
4 x = 1
5 -> import pdb; pdb.set_trace()
(Pdb) (lambda: x)()Pdb can not use the local variable 'x' to create a closure in your evaluation. Instead, it tries to use 'x' in globals(). This behavior is due to the limitation of dynamic interpretation within a debugger session. Hint: type 'help interact' to check 'interact' command.*** NameError: name 'x' is not defined
I believe it would be helpful and less annoyed for those who encounters this problem. Call for maintainers review, please.
PS.
-
Ruby does not have this problem. Whether it exists depends on how a programming language to implement closures. However, I think that what CPython do (by
PyCellObject) is smart and efficient. -
I tried to solve the problem by copying local variables to globals() (just like what
interactcommand do), but it results in more problems. The most typical one is that if I evalglobals()in such an affected environment, I will always get the wrong result. -
I also tried to patch and evaluate again what user inputs when catching a closure-related
NameError. Obviously, it is not a good idea due to side effects of evaluation. -
The last possible solution I think of is import
astand do some hacking ... it's overkill, and it also brings up other side effects.
-
Call for review again.
Maybe xdegaye would like to take a look?
I found this related issue: bpo-21161It seems that the last patch in bpo-21161 fixes all the problems described here, msg 149096 explains why. Can you confirm that this is a duplicate of bpo-21161.
The last patch in bpo-21161 fixes some problems but also brings up critical issues:
(Pdb) list .
1 y = 2
2
3 def f():
4 y = 9
5 z = 10
6 -> import pdb; pdb.set_trace();
7 f()
[EOF]
(Pdb) globals()['y']
9
(Pdb) global y; print(y)
9
(Pdb) globals()['z']
10I think that we should not copy local variables to globals() while doing execution. It will always bring out the wrong result of
globals().So, the patch I proposed here is focused on "Showing Friendly Error Message" to let the users be less confused.
# The patch works when a user tries to bound a free variable to a list comprehension. It will show the proper error message, too.
This patch fixes the problems raised in this issue and allows accessing the globals at the Pdb prompt with the globals() dictionary:
(Pdb) list
1 y = 2
2
3 def f():
4 y = 9
5 z = 10
6 -> import pdb; pdb.set_trace();
7 f()
[EOF]
(Pdb) globals()['y']
2Your solution is quite neat.
But it still misses use cases of theglobalstatement:1 y = 2
2
3 def f():
4 y = 9
5 -> import pdb; pdb.set_trace();
6
7 f()
(Pdb) global y; y
9
(Pdb) global y; y += 1; y
10
(Pdb) globals()['y']
2Hey xdegaye, have you confirmed it?
Fixed by #111094
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields: