Skip to content

Commit c0ecf21

Browse files
authored
gh-145055: Accept frozendict for globals in exec() and eval() (#145072)
1 parent c8aa8de commit c0ecf21

File tree

12 files changed

+66
-22
lines changed

12 files changed

+66
-22
lines changed

Doc/library/functions.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ are always available. They are listed here in alphabetical order.
594594

595595
:param globals:
596596
The global namespace (default: ``None``).
597-
:type globals: :class:`dict` | ``None``
597+
:type globals: :class:`dict` | :class:`frozendict` | ``None``
598598

599599
:param locals:
600600
The local namespace (default: ``None``).
@@ -660,6 +660,10 @@ are always available. They are listed here in alphabetical order.
660660
The semantics of the default *locals* namespace have been adjusted as
661661
described for the :func:`locals` builtin.
662662

663+
.. versionchanged:: next
664+
665+
*globals* can now be a :class:`frozendict`.
666+
663667
.. index:: pair: built-in function; exec
664668

665669
.. function:: exec(source, /, globals=None, locals=None, *, closure=None)
@@ -737,6 +741,10 @@ are always available. They are listed here in alphabetical order.
737741
The semantics of the default *locals* namespace have been adjusted as
738742
described for the :func:`locals` builtin.
739743

744+
.. versionchanged:: next
745+
746+
*globals* can now be a :class:`frozendict`.
747+
740748

741749
.. function:: filter(function, iterable, /)
742750

Include/internal/pycore_dict.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,15 +372,15 @@ _PyDict_UniqueId(PyDictObject *mp)
372372
static inline void
373373
_Py_INCREF_DICT(PyObject *op)
374374
{
375-
assert(PyDict_Check(op));
375+
assert(PyAnyDict_Check(op));
376376
Py_ssize_t id = _PyDict_UniqueId((PyDictObject *)op);
377377
_Py_THREAD_INCREF_OBJECT(op, id);
378378
}
379379

380380
static inline void
381381
_Py_DECREF_DICT(PyObject *op)
382382
{
383-
assert(PyDict_Check(op));
383+
assert(PyAnyDict_Check(op));
384384
Py_ssize_t id = _PyDict_UniqueId((PyDictObject *)op);
385385
_Py_THREAD_DECREF_OBJECT(op, id);
386386
}

Lib/test/test_builtin.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,16 @@ def __getitem__(self, key):
784784
raise ValueError
785785
self.assertRaises(ValueError, eval, "foo", {}, X())
786786

787+
def test_eval_frozendict(self):
788+
ns = frozendict(x=1, data=[], __builtins__=__builtins__)
789+
eval("data.append(x)", ns, ns)
790+
self.assertEqual(ns['data'], [1])
791+
792+
ns = frozendict()
793+
errmsg = "cannot assign __builtins__ to frozendict globals"
794+
with self.assertRaisesRegex(TypeError, errmsg):
795+
eval("", ns, ns)
796+
787797
def test_eval_kwargs(self):
788798
data = {"A_GLOBAL_VALUE": 456}
789799
self.assertEqual(eval("globals()['A_GLOBAL_VALUE']", globals=data), 456)
@@ -882,6 +892,21 @@ def test_exec(self):
882892
del l['__builtins__']
883893
self.assertEqual((g, l), ({'a': 1}, {'b': 2}))
884894

895+
def test_exec_frozendict(self):
896+
ns = frozendict(x=1, data=[], __builtins__=__builtins__)
897+
exec("data.append(x)", ns, ns)
898+
self.assertEqual(ns['data'], [1])
899+
900+
ns = frozendict(__builtins__=__builtins__)
901+
errmsg = "'frozendict' object does not support item assignment"
902+
with self.assertRaisesRegex(TypeError, errmsg):
903+
exec("x = 1", ns, ns)
904+
905+
ns = frozendict()
906+
errmsg = "cannot assign __builtins__ to frozendict globals"
907+
with self.assertRaisesRegex(TypeError, errmsg):
908+
exec("", ns, ns)
909+
885910
def test_exec_kwargs(self):
886911
g = {}
887912
exec('global z\nz = 1', globals=g)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`exec` and :func:`eval` now accept :class:`frozendict` for *globals*.
2+
Patch by Victor Stinner.

Objects/codeobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1830,7 +1830,7 @@ identify_unbound_names(PyThreadState *tstate, PyCodeObject *co,
18301830
assert(attrnames != NULL);
18311831
assert(PySet_Check(attrnames));
18321832
assert(PySet_GET_SIZE(attrnames) == 0 || counts != NULL);
1833-
assert(globalsns == NULL || PyDict_Check(globalsns));
1833+
assert(globalsns == NULL || PyAnyDict_Check(globalsns));
18341834
assert(builtinsns == NULL || PyDict_Check(builtinsns));
18351835
assert(counts == NULL || counts->total == 0);
18361836
struct co_unbound_counts unbound = {0};

Objects/dictobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2687,7 +2687,7 @@ _PyDict_LoadGlobalStackRef(PyDictObject *globals, PyDictObject *builtins, PyObje
26872687
PyObject *
26882688
_PyDict_LoadBuiltinsFromGlobals(PyObject *globals)
26892689
{
2690-
if (!PyDict_Check(globals)) {
2690+
if (!PyAnyDict_Check(globals)) {
26912691
PyErr_BadInternalCall();
26922692
return NULL;
26932693
}

Objects/funcobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ PyObject *
150150
PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)
151151
{
152152
assert(globals != NULL);
153-
assert(PyDict_Check(globals));
153+
assert(PyAnyDict_Check(globals));
154154
_Py_INCREF_DICT(globals);
155155

156156
PyCodeObject *code_obj = (PyCodeObject *)code;

Python/_warnings.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ setup_context(Py_ssize_t stack_level,
10451045

10461046
/* Setup registry. */
10471047
assert(globals != NULL);
1048-
assert(PyDict_Check(globals));
1048+
assert(PyAnyDict_Check(globals));
10491049
int rc = PyDict_GetItemRef(globals, &_Py_ID(__warningregistry__),
10501050
registry);
10511051
if (rc < 0) {
@@ -1269,10 +1269,11 @@ warnings_warn_explicit_impl(PyObject *module, PyObject *message,
12691269
}
12701270

12711271
if (module_globals && module_globals != Py_None) {
1272-
if (!PyDict_Check(module_globals)) {
1272+
if (!PyAnyDict_Check(module_globals)) {
12731273
PyErr_Format(PyExc_TypeError,
1274-
"module_globals must be a dict, not '%.200s'",
1275-
Py_TYPE(module_globals)->tp_name);
1274+
"module_globals must be a dict or a frozendict, "
1275+
"not %T",
1276+
module_globals);
12761277
return NULL;
12771278
}
12781279

Python/bltinmodule.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,10 +1040,11 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
10401040
PyErr_SetString(PyExc_TypeError, "locals must be a mapping");
10411041
return NULL;
10421042
}
1043-
if (globals != Py_None && !PyDict_Check(globals)) {
1043+
if (globals != Py_None && !PyAnyDict_Check(globals)) {
10441044
PyErr_SetString(PyExc_TypeError, PyMapping_Check(globals) ?
1045-
"globals must be a real dict; try eval(expr, {}, mapping)"
1046-
: "globals must be a dict");
1045+
"globals must be a real dict or a frozendict; "
1046+
"try eval(expr, {}, mapping)"
1047+
: "globals must be a dict or a frozendict");
10471048
return NULL;
10481049
}
10491050

@@ -1197,9 +1198,10 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
11971198
locals = Py_NewRef(globals);
11981199
}
11991200

1200-
if (!PyDict_Check(globals)) {
1201-
PyErr_Format(PyExc_TypeError, "exec() globals must be a dict, not %.100s",
1202-
Py_TYPE(globals)->tp_name);
1201+
if (!PyAnyDict_Check(globals)) {
1202+
PyErr_Format(PyExc_TypeError,
1203+
"exec() globals must be a dict or a frozendict, not %T",
1204+
globals);
12031205
goto error;
12041206
}
12051207
if (!PyMapping_Check(locals)) {

Python/ceval.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2718,7 +2718,7 @@ static PyObject *
27182718
get_globals_builtins(PyObject *globals)
27192719
{
27202720
PyObject *builtins = NULL;
2721-
if (PyDict_Check(globals)) {
2721+
if (PyAnyDict_Check(globals)) {
27222722
if (PyDict_GetItemRef(globals, &_Py_ID(__builtins__), &builtins) < 0) {
27232723
return NULL;
27242724
}
@@ -2743,6 +2743,10 @@ set_globals_builtins(PyObject *globals, PyObject *builtins)
27432743
}
27442744
else {
27452745
if (PyObject_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) {
2746+
if (PyFrozenDict_Check(globals)) {
2747+
PyErr_SetString(PyExc_TypeError,
2748+
"cannot assign __builtins__ to frozendict globals");
2749+
}
27462750
return -1;
27472751
}
27482752
}
@@ -3584,7 +3588,7 @@ _PyEval_GetANext(PyObject *aiter)
35843588
void
35853589
_PyEval_LoadGlobalStackRef(PyObject *globals, PyObject *builtins, PyObject *name, _PyStackRef *writeto)
35863590
{
3587-
if (PyDict_CheckExact(globals) && PyDict_CheckExact(builtins)) {
3591+
if (PyAnyDict_CheckExact(globals) && PyAnyDict_CheckExact(builtins)) {
35883592
_PyDict_LoadGlobalStackRef((PyDictObject *)globals,
35893593
(PyDictObject *)builtins,
35903594
name, writeto);

0 commit comments

Comments
 (0)