Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ are always available. They are listed here in alphabetical order.

:param globals:
The global namespace (default: ``None``).
:type globals: :class:`dict` | ``None``
:type globals: :class:`dict` | :class:`frozendict` | ``None``

:param locals:
The local namespace (default: ``None``).
Expand Down Expand Up @@ -643,6 +643,10 @@ are always available. They are listed here in alphabetical order.
If the given source is a string, then leading and trailing spaces and tabs
are stripped.

It's possible to pass :class:`frozendict` to *globals* if the
:class:`!frozendict` has a ``__builtins__`` item. In this case, it's not
possible to assign or reassign global variables.

See :func:`ast.literal_eval` for a function that can safely evaluate strings
with expressions containing only literals.

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

.. versionchanged:: next

*globals* can now be a :class:`frozendict`.

.. index:: pair: built-in function; exec

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

It's possible to pass :class:`frozendict` to *globals* if the
:class:`!frozendict` has a ``__builtins__`` item. In this case, it's not
possible to assign or reassign global variables.

.. note::

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

.. versionchanged:: next

*globals* can now be a :class:`frozendict`.


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

Expand Down
4 changes: 2 additions & 2 deletions Include/internal/pycore_dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,15 +370,15 @@ _PyDict_UniqueId(PyDictObject *mp)
static inline void
_Py_INCREF_DICT(PyObject *op)
{
assert(PyDict_Check(op));
assert(PyAnyDict_Check(op));
Py_ssize_t id = _PyDict_UniqueId((PyDictObject *)op);
_Py_THREAD_INCREF_OBJECT(op, id);
}

static inline void
_Py_DECREF_DICT(PyObject *op)
{
assert(PyDict_Check(op));
assert(PyAnyDict_Check(op));
Py_ssize_t id = _PyDict_UniqueId((PyDictObject *)op);
_Py_THREAD_DECREF_OBJECT(op, id);
}
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,12 @@ def __getitem__(self, key):
raise ValueError
self.assertRaises(ValueError, eval, "foo", {}, X())

# Pass frozenset to globals
ns = frozendict(x=1, data=[], __builtins__=__builtins__)
code = "data.append(x)"
eval(code, ns, ns)
self.assertEqual(ns['data'], [1])

def test_eval_kwargs(self):
data = {"A_GLOBAL_VALUE": 456}
self.assertEqual(eval("globals()['A_GLOBAL_VALUE']", globals=data), 456)
Expand Down Expand Up @@ -882,6 +888,18 @@ def test_exec(self):
del l['__builtins__']
self.assertEqual((g, l), ({'a': 1}, {'b': 2}))

# Pass frozenset to globals
ns = frozendict(x=1, data=[], __builtins__=__builtins__)
code = "data.append(x)"
exec(code, ns, ns)
self.assertEqual(ns['data'], [1])

ns = frozendict(__builtins__=__builtins__)
code = "x = 1"
errmsg = "'frozendict' object does not support item assignment"
with self.assertRaisesRegex(TypeError, errmsg):
exec(code, ns, ns)

def test_exec_kwargs(self):
g = {}
exec('global z\nz = 1', globals=g)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`exec` and :func:`eval` now accept :class:`frozendict` for *globals*.
Patch by Victor Stinner.
2 changes: 1 addition & 1 deletion Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1830,7 +1830,7 @@ identify_unbound_names(PyThreadState *tstate, PyCodeObject *co,
assert(attrnames != NULL);
assert(PySet_Check(attrnames));
assert(PySet_GET_SIZE(attrnames) == 0 || counts != NULL);
assert(globalsns == NULL || PyDict_Check(globalsns));
assert(globalsns == NULL || PyAnyDict_Check(globalsns));
assert(builtinsns == NULL || PyDict_Check(builtinsns));
assert(counts == NULL || counts->total == 0);
struct co_unbound_counts unbound = {0};
Expand Down
2 changes: 1 addition & 1 deletion Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2661,7 +2661,7 @@ _PyDict_LoadGlobalStackRef(PyDictObject *globals, PyDictObject *builtins, PyObje
PyObject *
_PyDict_LoadBuiltinsFromGlobals(PyObject *globals)
{
if (!PyDict_Check(globals)) {
if (!PyAnyDict_Check(globals)) {
PyErr_BadInternalCall();
return NULL;
}
Expand Down
2 changes: 1 addition & 1 deletion Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ PyObject *
PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)
{
assert(globals != NULL);
assert(PyDict_Check(globals));
assert(PyAnyDict_Check(globals));
_Py_INCREF_DICT(globals);

PyCodeObject *code_obj = (PyCodeObject *)code;
Expand Down
4 changes: 2 additions & 2 deletions Python/_warnings.c
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ setup_context(Py_ssize_t stack_level,

/* Setup registry. */
assert(globals != NULL);
assert(PyDict_Check(globals));
assert(PyAnyDict_Check(globals));
int rc = PyDict_GetItemRef(globals, &_Py_ID(__warningregistry__),
registry);
if (rc < 0) {
Expand Down Expand Up @@ -1269,7 +1269,7 @@ warnings_warn_explicit_impl(PyObject *module, PyObject *message,
}

if (module_globals && module_globals != Py_None) {
if (!PyDict_Check(module_globals)) {
if (!PyAnyDict_Check(module_globals)) {
PyErr_Format(PyExc_TypeError,
"module_globals must be a dict, not '%.200s'",
Py_TYPE(module_globals)->tp_name);
Expand Down
14 changes: 8 additions & 6 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1034,10 +1034,11 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
PyErr_SetString(PyExc_TypeError, "locals must be a mapping");
return NULL;
}
if (globals != Py_None && !PyDict_Check(globals)) {
if (globals != Py_None && !PyAnyDict_Check(globals)) {
PyErr_SetString(PyExc_TypeError, PyMapping_Check(globals) ?
"globals must be a real dict; try eval(expr, {}, mapping)"
: "globals must be a dict");
"globals must be a real dict or a frozendict; "
"try eval(expr, {}, mapping)"
: "globals must be a dict or a frozendict");
return NULL;
}

Expand Down Expand Up @@ -1191,9 +1192,10 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
locals = Py_NewRef(globals);
}

if (!PyDict_Check(globals)) {
PyErr_Format(PyExc_TypeError, "exec() globals must be a dict, not %.100s",
Py_TYPE(globals)->tp_name);
if (!PyAnyDict_Check(globals)) {
PyErr_Format(PyExc_TypeError,
"exec() globals must be a dict or a frozendict, not %T",
globals);
goto error;
}
if (!PyMapping_Check(locals)) {
Expand Down
4 changes: 2 additions & 2 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2717,7 +2717,7 @@ static PyObject *
get_globals_builtins(PyObject *globals)
{
PyObject *builtins = NULL;
if (PyDict_Check(globals)) {
if (PyAnyDict_Check(globals)) {
if (PyDict_GetItemRef(globals, &_Py_ID(__builtins__), &builtins) < 0) {
return NULL;
}
Expand Down Expand Up @@ -3572,7 +3572,7 @@ _PyEval_GetANext(PyObject *aiter)
void
_PyEval_LoadGlobalStackRef(PyObject *globals, PyObject *builtins, PyObject *name, _PyStackRef *writeto)
{
if (PyDict_CheckExact(globals) && PyDict_CheckExact(builtins)) {
if (PyAnyDict_CheckExact(globals) && PyAnyDict_CheckExact(builtins)) {
_PyDict_LoadGlobalStackRef((PyDictObject *)globals,
(PyDictObject *)builtins,
name, writeto);
Expand Down
2 changes: 1 addition & 1 deletion Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -3728,7 +3728,7 @@ resolve_name(PyThreadState *tstate, PyObject *name, PyObject *globals, int level
_PyErr_SetString(tstate, PyExc_KeyError, "'__name__' not in globals");
goto error;
}
if (!PyDict_Check(globals)) {
if (!PyAnyDict_Check(globals)) {
_PyErr_SetString(tstate, PyExc_TypeError, "globals must be a dict");
goto error;
}
Expand Down
2 changes: 1 addition & 1 deletion Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -1347,7 +1347,7 @@ static PyObject *
run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, PyObject *locals)
{
/* Set globals['__builtins__'] if it doesn't exist */
if (!globals || !PyDict_Check(globals)) {
if (!globals || !PyAnyDict_Check(globals)) {
PyErr_SetString(PyExc_SystemError, "globals must be a real dict");
return NULL;
}
Expand Down
Loading