Repository navigation
Include Pdb._exec_in_closure() as code-path in exec() itself, or make it a public utility otherwise? #136280
Description
Activity
- addedtype-featureA feature request or enhancementA feature request or enhancement
on Jul 4, 2025 - addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Jul 4, 2025 First of all, could you make the example code show your actual ideas next time? Your current code will just raise an exception because there's no
f_back. A helpful reproducible example is something that we can copy & paste then execute.To answer your questions. I don't believe this has anything to do with
FrameLocalsProxy. It will have the exact same issue if you passlocals(). It's the code you compile - the generator believesxis a global variable but it does not exist in the global name space.So your question a) is not valid - it's not the
FrameLocalsProxythat caused the problem.Can we use the pdb's closure implementation in exec? No we can't do that. That would break backwards compatibility. Also it's very implicit. The fact
exec()takes aclosureargument shows that you should deal with it in your debugger - it's an advanced function and we want to keep it relatively straightforward. You have the option to do advanced things, but the default behavior does not assume the code was executed in a potential closure environment.And we probably won't expose the internal function of pdb either - as you said, it's not perfect. It's kind of a workaround and we don't want to make a promise to it.
Actually, making it to work is very difficult. I must disagree on your thought that "most of the problems could be resolved with a little bit more work", because I've spent plenty of time into it and did not find any good solution without hacking heavily on the code.
The problem will not be fully solved even if generators are inlined - the user can always create a function scope in their code.
I understand that I've talked plenty but did not give any helpful directions, sorry about that. I did put plenty of thoughts into this and if you have a better idea of how to make it work better, I'm happy to look at the solution. I did look at how the original author of that issue solved it in their project - it's a lot of code and full of black magics. It's impossible for us to maintain that in stdlib.
First of all, could you make the example code show your actual ideas next time? Your current code will just raise an exception because there's no
f_back.Apologies, you are probably right. This is what I see
I do not understand why you say there is no
f_back:
A helpful reproducible example is something that we can copy & paste then execute.
I know that. This is what I tried to achieve, I understand this is not the example you would expect. Apologies for annoying you but it is hard for me to isolate the thing from all of the IPython codebase.
I don't believe this has anything to do with
FrameLocalsProxy. It will have the exact same issue if you passlocals(). It's the code you compile - the generator believes x is a global variable but it does not exist in the global name space.I don't understand why you say that.

Your
locals()andf_localsare not equivalent. Yourf_localsis not the local scope of your interpreter, it's thepyreplobject. So you are running everything in the interpreter and that makes it a bit complicated.If you do
call_frame = sys._getframe(0)
instead of
call_frame = sys._getframe(0).f_back
You'll find that everything just works - that's the equivalent form as your
locals()solution. It's not aboutFrameLocalsProxy, you are just using different frames.Normally when we try to reproduce an example, we copy the lines in a file, save it and run it with python. If you do that you'll realize that
call_frame = sys._getframe(0).f_backgivesNonebecausesys._getframe(0)is already at the bottom. It does not raise an exception because of how current Python shell works.Your f_locals is not the local scope of your interpreter, it's the
pyreplobject.I know. I think it would make more sense to you in context of ipython/ipython#62
So you are running everything in the interpreter and that makes it a bit complicated
Yes, I know. I said I am coming from IPython :)
Normally when we try to reproduce an example, we copy the lines in a file, save it and run it with python. If you do that you'll realize that call_frame = sys._getframe(0).f_back gives None because sys._getframe(0) is already at the bottom. It does not raise an exception because of how current Python shell works.
Fine, indeed my reproducer assumed that one would be running it in a Python shell. It still stands that this is a real issue, and this is the smallest reproducer I was able to create.
Anyways, since it seems you are not very enthusiastic about adding a code path to
execwhich would handle generators well (whether that would be triggered byFrameLocalsProxytype or a new argument or whatever - you know better than me), feel free to close this issue.This is a real issue, it just has nothing to do with
FrameLocalsProxy. The problem is that when we compile a piece of code, we have no way to know that it is supposed to be in another function (closured). We can't change that behavior because it's not backward compatible - it's not that we don't want to make it better, we can't make it better without breaking a lot of the old code.The way
pdbdeals with it is very hacky and should only live in a debugger. You need to change a lot of things to makeexecreally work in such scenario - I think you probably need to change the compiler.Reacted by Michał Krassowski
Feature or enhancement
Proposal:
When IPython is embedded into a non-IPython interpreter, it evaluates code using frame locals (
FrameLocalsProxy) in the same way aspdbdoes in Python stdlib.This does not work with objects which require access to the closure scope:
This bug affected
pdbmodule too (#65360) but it was fixed/worked around by:For example, the following does not work:
Produces:
I know that
FrameLocalsProxyhas a few undocumented limitations, I am not sure if those are relevant here - I am linking the relevant issue just in case if that helps maintainers to refresh the context:I am coming here from IPython, to explore if the solution belongs in IPython, or in CPython repo:
exechandleFrameLocalsProxyspecially in some way?pdbin gh-83151: Make closure work on pdb #111094 (_exec_in_closuremethod) be included in the defaultexecimplementation for whenFrameLocalsProxyis passed?_exec_in_closureintoexecproper does not make sense, is it a good idea to explore moving_exec_in_closureout ofpdband making it public?The fix/workaround from #111094 is not perfect yet, though I think most of the problems could be resolved with a little bit more work. These issues were reported in:
If
_exec_in_closurewere exposed as public, there would be a bigger incentive for community to fix issues outlined in #126958, basically centralising the effort to make that work well.On the other hand, if there are plans to inline generators in the future (I saw that PEP 709 leaved that as a possibility), maybe
_exec_in_closurewould no longer be necessary in the first place.As an alternative to reusing
_exec_in_closure, IPython could uselocals()call to populatelocals_nswhich get passed down toexecvialocalsargument when in embed mode; I am somewhat apprehensive to make this change as I worry it might break downstream code.However, if you all advise that
_exec_in_closureshall remain a privatepdbutility, this would likely tilt the the trade-off towards using thelocals()call, as otherwise IPython would need to maintain its own version of_exec_in_closureadding to maintenance cost on already over-stretched team.Has this already been discussed elsewhere?
This is a minor feature, which does not need previous discussion elsewhere
Links to previous discussion of this feature:
Not directly, but this is the third oldest unresolved issue in IPython dating back over 15 years:
CC @gaogaotiantian if I may, as author of #111094 and assignee on #126958