Skip to content

Commit 9440f8f

Browse files
committed
gh-145055: Accept frozendict for globals in exec() and eval()
1 parent 20b1535 commit 9440f8f

12 files changed

Lines changed: 56 additions & 18 deletions

File tree

Doc/library/functions.rst

Lines changed: 17 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``).
@@ -643,6 +643,10 @@ are always available. They are listed here in alphabetical order.
643643
If the given source is a string, then leading and trailing spaces and tabs
644644
are stripped.
645645

646+
It's possible to pass :class:`frozendict` to *globals* if the
647+
:class:`!frozendict` has a ``__builtins__`` item. In this case, it's not
648+
possible to assign or reassign global variables.
649+
646650
See :func:`ast.literal_eval` for a function that can safely evaluate strings
647651
with expressions containing only literals.
648652

@@ -660,6 +664,10 @@ are always available. They are listed here in alphabetical order.
660664
The semantics of the default *locals* namespace have been adjusted as
661665
described for the :func:`locals` builtin.
662666

667+
.. versionchanged:: next
668+
669+
*globals* can now be a :class:`frozendict`.
670+
663671
.. index:: pair: built-in function; exec
664672

665673
.. function:: exec(source, /, globals=None, locals=None, *, closure=None)
@@ -688,6 +696,10 @@ are always available. They are listed here in alphabetical order.
688696
respectively. If provided, *locals* can be any mapping object. Remember
689697
that at the module level, globals and locals are the same dictionary.
690698

699+
It's possible to pass :class:`frozendict` to *globals* if the
700+
:class:`!frozendict` has a ``__builtins__`` item. In this case, it's not
701+
possible to assign or reassign global variables.
702+
691703
.. note::
692704

693705
When ``exec`` gets two separate objects as *globals* and *locals*, the
@@ -737,6 +749,10 @@ are always available. They are listed here in alphabetical order.
737749
The semantics of the default *locals* namespace have been adjusted as
738750
described for the :func:`locals` builtin.
739751

752+
.. versionchanged:: next
753+
754+
*globals* can now be a :class:`frozendict`.
755+
740756

741757
.. function:: filter(function, iterable, /)
742758

Include/internal/pycore_dict.h

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

378378
static inline void
379379
_Py_DECREF_DICT(PyObject *op)
380380
{
381-
assert(PyDict_Check(op));
381+
assert(PyAnyDict_Check(op));
382382
Py_ssize_t id = _PyDict_UniqueId((PyDictObject *)op);
383383
_Py_THREAD_DECREF_OBJECT(op, id);
384384
}

Lib/test/test_builtin.py

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

787+
# Pass frozenset to globals
788+
ns = frozendict(x=1, data=[], __builtins__=__builtins__)
789+
code = "data.append(x)"
790+
eval(code, ns, ns)
791+
self.assertEqual(ns['data'], [1])
792+
787793
def test_eval_kwargs(self):
788794
data = {"A_GLOBAL_VALUE": 456}
789795
self.assertEqual(eval("globals()['A_GLOBAL_VALUE']", globals=data), 456)
@@ -882,6 +888,18 @@ def test_exec(self):
882888
del l['__builtins__']
883889
self.assertEqual((g, l), ({'a': 1}, {'b': 2}))
884890

891+
# Pass frozenset to globals
892+
ns = frozendict(x=1, data=[], __builtins__=__builtins__)
893+
code = "data.append(x)"
894+
exec(code, ns, ns)
895+
self.assertEqual(ns['data'], [1])
896+
897+
ns = frozendict(__builtins__=__builtins__)
898+
code = "x = 1"
899+
errmsg = "'frozendict' object does not support item assignment"
900+
with self.assertRaisesRegex(TypeError, errmsg):
901+
exec(code, ns, ns)
902+
885903
def test_exec_kwargs(self):
886904
g = {}
887905
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
@@ -2661,7 +2661,7 @@ _PyDict_LoadGlobalStackRef(PyDictObject *globals, PyDictObject *builtins, PyObje
26612661
PyObject *
26622662
_PyDict_LoadBuiltinsFromGlobals(PyObject *globals)
26632663
{
2664-
if (!PyDict_Check(globals)) {
2664+
if (!PyAnyDict_Check(globals)) {
26652665
PyErr_BadInternalCall();
26662666
return NULL;
26672667
}

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: 2 additions & 2 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,7 +1269,7 @@ 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,
12741274
"module_globals must be a dict, not '%.200s'",
12751275
Py_TYPE(module_globals)->tp_name);

Python/bltinmodule.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,10 +1034,11 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
10341034
PyErr_SetString(PyExc_TypeError, "locals must be a mapping");
10351035
return NULL;
10361036
}
1037-
if (globals != Py_None && !PyDict_Check(globals)) {
1037+
if (globals != Py_None && !PyAnyDict_Check(globals)) {
10381038
PyErr_SetString(PyExc_TypeError, PyMapping_Check(globals) ?
1039-
"globals must be a real dict; try eval(expr, {}, mapping)"
1040-
: "globals must be a dict");
1039+
"globals must be a real dict or a frozendict; "
1040+
"try eval(expr, {}, mapping)"
1041+
: "globals must be a dict or a frozendict");
10411042
return NULL;
10421043
}
10431044

@@ -1191,9 +1192,10 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
11911192
locals = Py_NewRef(globals);
11921193
}
11931194

1194-
if (!PyDict_Check(globals)) {
1195-
PyErr_Format(PyExc_TypeError, "exec() globals must be a dict, not %.100s",
1196-
Py_TYPE(globals)->tp_name);
1195+
if (!PyAnyDict_Check(globals)) {
1196+
PyErr_Format(PyExc_TypeError,
1197+
"exec() globals must be a dict or a frozendict, not %T",
1198+
globals);
11971199
goto error;
11981200
}
11991201
if (!PyMapping_Check(locals)) {

Python/ceval.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2717,7 +2717,7 @@ static PyObject *
27172717
get_globals_builtins(PyObject *globals)
27182718
{
27192719
PyObject *builtins = NULL;
2720-
if (PyDict_Check(globals)) {
2720+
if (PyAnyDict_Check(globals)) {
27212721
if (PyDict_GetItemRef(globals, &_Py_ID(__builtins__), &builtins) < 0) {
27222722
return NULL;
27232723
}
@@ -3572,7 +3572,7 @@ _PyEval_GetANext(PyObject *aiter)
35723572
void
35733573
_PyEval_LoadGlobalStackRef(PyObject *globals, PyObject *builtins, PyObject *name, _PyStackRef *writeto)
35743574
{
3575-
if (PyDict_CheckExact(globals) && PyDict_CheckExact(builtins)) {
3575+
if (PyAnyDict_CheckExact(globals) && PyAnyDict_CheckExact(builtins)) {
35763576
_PyDict_LoadGlobalStackRef((PyDictObject *)globals,
35773577
(PyDictObject *)builtins,
35783578
name, writeto);

0 commit comments

Comments
 (0)