-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
missing global names inside a class body (calling exec
with a ChainMap
)
#121306
Comments
The docs say (emphasis mine):
I don't think this one should count as a bug. But maybe it could count as a bug in the sense that it raises a NameError (like, it doesn't seem to check the exact type) |
Thank you! I found what you quoted in the docs here. Looks like raising a That's a bit sad. I've been misusing this for a year in several packages. I used to think using a mixined On the other hand, may I ask how can I implement layered context for {..., '__builtins__': ChainMap(..., builtins.__dict__)} |
We need to address this issue for whether we should allow a dict subclass to be passed in as This will never fully work in the current interpreter as Also the documentation is not completely correct - even if the user passes both So, we have a couple of options:
@markshannon any suggestion here as you are the owner of the interpreter now. |
Great analysis. I vote for option 2 because I believe there are more people who care more about versatility than performance when they choose Python over other programming languages for a task. Note that there's a significant amount of code that calls |
I have a branch with the exact check and I needed to fix a lot of tests... honestly I'd prefer amending the docs. But making LOAD_NAME works for nonexact dict might be a not so good idea if you lose performance. Maybe we can have two implementations for that when you assume that it's a dictionary and otherwise a (mutable) mapping? |
I only looked at
We had a lot of great features when all local variables are in a real dict and we moved forward to the current fast locals implementation. I don't think we should sacrifice such a common bytecode just for some super dark corner. The reason I said it was a super dark corner is because - I'm leaning forward to enforcing globals to be an exact dict, and that's basically our assumption now. |
This comment was marked as outdated.
This comment was marked as outdated.
After investigating a bit more tests (e.g., in I have an important question. Why does it assume that it's a dict (and it still works when it's not a dict but a subclass of a dict without a mixin?) Is it only because of
I really want to known which invariants are expected for it to work (at least, document it internally if it's not already the case (I don't know where to look at for that)). Why would it work for |
As the one who posted this issue, I want to say something. Although I am not familiar with CPython, I do not want to hurt its performance. If allowing However, using a subclass does work in most cases. The easiest way is to just subclass a The only problem I encountered is that when accessing variables in a class. I've tried using class D(D):
def __missing__(self, k):
import builtins
return builtins.__dict__.get(k, k) Everytime I access a missing key, it should return the key as-is: eval('a', D()) # -> 'a' But in a class scope, I still can't access the value: exec('''
def f():
print(a)
f()
class A:
def f(self):
print(b)
A.f(0)
A().f()
class B:
print(c)
''', D()) Only the last
I don't know what happens when the interpreter enters a class context. I believe some copy happens here but the type doesn't remains, so accessing 'c' is done on a pure So, my thought is: although the docs say that it can't be a subclass of edit (2024/7/6):I found that wrapping the code in a function definition can make it support Code
from collections import ChainMap
class ChainMap(ChainMap, dict): # globals must be a real dict
pass
source = """
def _():
a = 1
class A:
print(a)
_()
"""
exec(source, ChainMap()) # 1 |
Basically, there's a giant hole here - it's about
Your access a "global variable" inside a class will become a Therefore, the whole thing is a mess now and we should probably set up a rule for this - do we allow a dict-like global? If so, how alike? In any case, we need extra input here. Let's wait for @markshannon 's opinion, and possibly other core devs. |
While I totally agree with you on the need to standardize the assumptions about the globals dict from the implementations of different instructions involving globals, the lack of such a standardization is not really an obstacle to allowing a mapping as the globals argument to This can be done by adding a Line 1612 in 59be79a
The performance impact with this approach should be minimal since Please feel free to poke holes at my PR #121389. |
Allowing an arbitrary mapping for |
Sorry, but can you elaborate on exactly in which ways allowing a mapping to the existing API for To quote your assessment above:
|
I thought you are changing |
Ah I see. Yeah my modifications are strictly about adding calls to the mapping API as a fallback so what works right now should continue to work.
I think the OP's use case of a layered context is a good one, as I also had such needs for dynamic injection of namespace before and had to resort to uglier workarounds. Python is known for its customizability, and a generalization of globals as a mapping like locals and builtins already are enables users to tailor namespace behaviors closer to the mental model of such projects. For my PR though I've limited the scope of change to the execution path of @markshannon can you help with some feedbacks when you get a chance? Thanks! |
I don't see a good reason why the globals must be a dictionary in some places, but not in others that appear very similar. If we are going to be consistent, it must be to allow general mappings, as restricting globals to a dictionary in all cases would be a breaking change. Since our stats show that the only instruction under discussion that is performance critical is |
Agreed. I was hesitant to allow a module's |
I've updated the PR to allow a mapping to be the |
Bug report
Bug description:
I need to implement layered context running python code, so I choose
ChainMap
. But Python needs everyglobals
passed toexec
to be adict
, so I mixins it intoChainMap
.This patch runs well as far, but in a class it can't get the value from the outside.
I think this may because in the class context CPython uses some function like
PyDict_Get
instead of__getitem__
, so the values didn't get copied into the class's globals context.This bug caused this code fails:
Other reproduction approaches
This is reproduceable in
pyodide
(a wasm port of CPython 3.12.1), so you can run the code above by just clicking one of the following link:CPython versions tested on:
3.12
Operating systems tested on:
Linux, Windows
Linked PRs
The text was updated successfully, but these errors were encountered: