Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-135443: Sometimes Fall Back to __main__.__dict__ For Globals #135491

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

Merged
merged 11 commits into from
Jun 16, 2025
Merged
10 changes: 10 additions & 0 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,16 @@ static inline void _Py_LeaveRecursiveCall(void) {

extern _PyInterpreterFrame* _PyEval_GetFrame(void);

extern PyObject * _PyEval_GetGlobalsFromRunningMain(PyThreadState *);
extern int _PyEval_EnsureBuiltins(
PyThreadState *,
PyObject *,
PyObject **p_builtins);
extern int _PyEval_EnsureBuiltinsWithModule(
PyThreadState *,
PyObject *,
PyObject **p_builtins);

PyAPI_FUNC(PyObject *)_Py_MakeCoro(PyFunctionObject *func);

/* Handle signals, pending calls, GIL drop request
Expand Down
107 changes: 107 additions & 0 deletions Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,113 @@ def test_call_invalid(self):
with self.assertRaises(interpreters.NotShareableError):
interp.call(func, op, 'eggs!')

def test_callable_requires_frame(self):
# There are various functions that require a current frame.
interp = interpreters.create()
for call, expected in [
((eval, '[1, 2, 3]'),
[1, 2, 3]),
((eval, 'sum([1, 2, 3])'),
6),
((exec, '...'),
None),
]:
with self.subTest(str(call)):
res = interp.call(*call)
self.assertEqual(res, expected)

result_not_pickleable = [
globals,
locals,
vars,
]
for func, expectedtype in {
globals: dict,
locals: dict,
vars: dict,
dir: list,
}.items():
with self.subTest(str(func)):
if func in result_not_pickleable:
with self.assertRaises(interpreters.NotShareableError):
interp.call(func)
else:
res = interp.call(func)
self.assertIsInstance(res, expectedtype)
self.assertIn('__builtins__', res)

def test_globals_from_builtins(self):
# The builtins exec(), eval(), globals(), locals(), vars(),
# and dir() each runs relative to the target interpreter's
# __main__ module, when called directly. However,
# globals(), locals(), and vars() don't work when called
# directly so we don't check them.
from _frozen_importlib import BuiltinImporter
interp = interpreters.create()

names = interp.call(dir)
self.assertEqual(names, [
'__builtins__',
'__doc__',
'__loader__',
'__name__',
'__package__',
'__spec__',
])

values = {name: interp.call(eval, name)
for name in names if name != '__builtins__'}
self.assertEqual(values, {
'__name__': '__main__',
'__doc__': None,
'__spec__': None, # It wasn't imported, so no module spec?
'__package__': None,
'__loader__': BuiltinImporter,
})
with self.assertRaises(ExecutionFailed):
interp.call(eval, 'spam'),

interp.call(exec, f'assert dir() == {names}')

# Update the interpreter's __main__.
interp.prepare_main(spam=42)
expected = names + ['spam']

names = interp.call(dir)
self.assertEqual(names, expected)

value = interp.call(eval, 'spam')
self.assertEqual(value, 42)

interp.call(exec, f'assert dir() == {expected}, dir()')

def test_globals_from_stateless_func(self):
# A stateless func, which doesn't depend on any globals,
# doesn't go through pickle, so it runs in __main__.
def set_global(name, value):
globals()[name] = value

def get_global(name):
return globals().get(name)

interp = interpreters.create()

modname = interp.call(get_global, '__name__')
self.assertEqual(modname, '__main__')

res = interp.call(get_global, 'spam')
self.assertIsNone(res)

interp.exec('spam = True')
res = interp.call(get_global, 'spam')
self.assertTrue(res)

interp.call(set_global, 'spam', 42)
res = interp.call(get_global, 'spam')
self.assertEqual(res, 42)

interp.exec('assert spam == 42, repr(spam)')

def test_call_in_thread(self):
interp = interpreters.create()

Expand Down
20 changes: 18 additions & 2 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2083,9 +2083,25 @@ _dir_locals(void)
PyObject *names;
PyObject *locals;

locals = _PyEval_GetFrameLocals();
if (locals == NULL)
if (_PyEval_GetFrame() != NULL) {
locals = _PyEval_GetFrameLocals();
}
else {
PyThreadState *tstate = _PyThreadState_GET();
locals = _PyEval_GetGlobalsFromRunningMain(tstate);
if (locals == NULL) {
if (!_PyErr_Occurred(tstate)) {
locals = _PyEval_GetFrameLocals();
assert(_PyErr_Occurred(tstate));
}
}
else {
Py_INCREF(locals);
}
}
if (locals == NULL) {
return NULL;
}

names = PyMapping_Keys(locals);
Py_DECREF(locals);
Expand Down
Loading
Loading