diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 17348dd907bf67..ebb5c59e2b3774 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -581,7 +581,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: :term:`mapping` | ``None`` :param locals: The local namespace (default: ``None``). @@ -592,14 +592,14 @@ are always available. They are listed here in alphabetical order. The *expression* argument is parsed and evaluated as a Python expression (technically speaking, a condition list) using the *globals* and *locals* - mappings as global and local namespace. If the *globals* dictionary is + mappings as global and local namespace. If the *globals* mapping is present and does not contain a value for the key ``__builtins__``, a reference to the dictionary of the built-in module :mod:`builtins` is inserted under that key before *expression* is parsed. That way you can control what builtins are available to the executed code by inserting your - own ``__builtins__`` dictionary into *globals* before passing it to + own ``__builtins__`` mapping into *globals* before passing it to :func:`eval`. If the *locals* mapping is omitted it defaults to the - *globals* dictionary. If both mappings are omitted, the expression is + *globals* mapping. If both mappings are omitted, the expression is executed with the *globals* and *locals* in the environment where :func:`eval` is called. Note, *eval()* will only have access to the :term:`nested scopes ` (non-locals) in the enclosing @@ -619,7 +619,7 @@ are always available. They are listed here in alphabetical order. Hints: dynamic execution of statements is supported by the :func:`exec` function. The :func:`globals` and :func:`locals` functions - return the current global and local dictionary, respectively, which may be + return the current global and local mapping, respectively, which may be useful to pass around for use by :func:`eval` or :func:`exec`. If the given source is a string, then leading and trailing spaces and tabs @@ -642,6 +642,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:: 3.14 + + A mapping can now be passed as the *globals* argument. + .. index:: pair: built-in function; exec .. function:: exec(source, /, globals=None, locals=None, *, closure=None) @@ -658,12 +662,12 @@ are always available. They are listed here in alphabetical order. :func:`exec` function. The return value is ``None``. In all cases, if the optional parts are omitted, the code is executed in the - current scope. If only *globals* is provided, it must be a dictionary - (and not a subclass of dictionary), which + current scope. If only *globals* is provided, it must be a mapping, which will be used for both the global and the local variables. If *globals* and *locals* are given, they are used for the global and local variables, - respectively. If provided, *locals* can be any mapping object. Remember - that at the module level, globals and locals are the same dictionary. + respectively. If provided, *globals* and *locals* can be any mapping + objects. Remember that at the module level, globals and locals are the same + mapping. .. note:: @@ -673,11 +677,11 @@ are always available. They are listed here in alphabetical order. to access variables assigned at the top level (as the "top level" variables are treated as class variables in a class definition). - If the *globals* dictionary does not contain a value for the key - ``__builtins__``, a reference to the dictionary of the built-in module + If the *globals* mapping does not contain a value for the key + ``__builtins__``, a reference to the mapping of the built-in module :mod:`builtins` is inserted under that key. That way you can control what builtins are available to the executed code by inserting your own - ``__builtins__`` dictionary into *globals* before passing it to :func:`exec`. + ``__builtins__`` mapping into *globals* before passing it to :func:`exec`. The *closure* argument specifies a closure--a tuple of cellvars. It's only valid when the *object* is a code object containing free variables. @@ -698,7 +702,7 @@ are always available. They are listed here in alphabetical order. .. note:: The default *locals* act as described for function :func:`locals` below. - Pass an explicit *locals* dictionary if you need to see effects of the + Pass an explicit *locals* mapping if you need to see effects of the code on *locals* after function :func:`exec` returns. .. versionchanged:: 3.11 @@ -713,6 +717,9 @@ 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:: 3.14 + + A mapping can now be passed as the *globals* argument. .. function:: filter(function, iterable) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index d3f7cfb01d3c21..148d48104ba6ac 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -5293,12 +5293,12 @@ foo`` does not require a module object named *foo* to exist, rather it requires an (external) *definition* for a module named *foo* somewhere.) A special attribute of every module is :attr:`~object.__dict__`. This is the -dictionary containing the module's symbol table. Modifying this dictionary will -actually change the module's symbol table, but direct assignment to the +:term:`mapping` containing the module's symbol table. Modifying this mapping +will actually change the module's symbol table, but direct assignment to the :attr:`~object.__dict__` attribute is not possible (you can write ``m.__dict__['a'] = 1``, which defines ``m.a`` to be ``1``, but you can't write -``m.__dict__ = {}``). Modifying :attr:`~object.__dict__` directly is -not recommended. +``m.__dict__ = {}``). Modifying :attr:`~object.__dict__` directly is not +recommended. Modules built into the interpreter are written like this: ````. If loaded from a file, they are written as ``` that holds the function's + - A reference to the :term:`mapping` that holds the function's :ref:`global variables ` -- the global namespace of the module in which the function was defined. @@ -868,15 +868,15 @@ the :ref:`import system ` as invoked either by the :keyword:`import` statement, or by calling functions such as :func:`importlib.import_module` and built-in :func:`__import__`. A module object has a namespace implemented by a -:class:`dictionary ` object (this is the dictionary referenced by the +:term:`mapping` object (this is the mapping referenced by the :attr:`~function.__globals__` attribute of functions defined in the module). Attribute references are -translated to lookups in this dictionary, e.g., ``m.x`` is equivalent to +translated to lookups in this mapping, e.g., ``m.x`` is equivalent to ``m.__dict__["x"]``. A module object does not contain the code object used to initialize the module (since it isn't needed once the initialization is done). -Attribute assignment updates the module's namespace dictionary, e.g., +Attribute assignment updates the module's namespace mapping, e.g., ``m.x = 1`` is equivalent to ``m.__dict__["x"] = 1``. .. index:: @@ -913,22 +913,27 @@ Predefined (writable) attributes: .. index:: single: __dict__ (module attribute) Special read-only attribute: :attr:`~object.__dict__` is the module's -namespace as a dictionary object. +namespace as a mapping object. .. impl-detail:: - Because of the way CPython clears module dictionaries, the module - dictionary will be cleared when the module falls out of scope even if the - dictionary still has live references. To avoid this, copy the dictionary - or keep the module around while using its dictionary directly. + Because of the way CPython clears module's namespace mapping, the module + namespace mapping will be cleared when the module falls out of scope even if + the namespace mapping still has live references. To avoid this, copy the + namespace mapping or keep the module around while using its namespace + mapping directly. + +.. versionchanged:: 3.14 + + The :attr:`~object.__dict__` attribute may now be a mapping. Custom classes -------------- Custom class types are typically created by class definitions (see section -:ref:`class`). A class has a namespace implemented by a dictionary object. -Class attribute references are translated to lookups in this dictionary, e.g., +:ref:`class`). A class has a namespace implemented by a mapping object. +Class attribute references are translated to lookups in this mapping, e.g., ``C.x`` is translated to ``C.__dict__["x"]`` (although there are a number of hooks which allow for other means of locating attributes). When the attribute name is not found there, the attribute search continues in the base classes. @@ -958,8 +963,8 @@ retrieved from a class may differ from those actually contained in its .. index:: triple: class; attribute; assignment -Class attribute assignments update the class's dictionary, never the dictionary -of a base class. +Class attribute assignments update the class's attribute mapping, never the +attribute mapping of a base class. .. index:: pair: class object; call @@ -985,7 +990,11 @@ Special attributes: The name of the module in which the class was defined. :attr:`~object.__dict__` - The dictionary containing the class's namespace. + The mapping containing the class's namespace. + + .. versionchanged:: 3.14 + + The :attr:`~object.__dict__` attribute may now be a mapping. :attr:`~class.__bases__` A tuple containing the base classes, in the order of @@ -1023,7 +1032,7 @@ Class instances pair: class instance; attribute A class instance is created by calling a class object (see above). A class -instance has a namespace implemented as a dictionary which is the first place +instance has a namespace implemented as a mapping which is the first place in which attribute references are searched. When an attribute is not found there, and the instance's class has an attribute by that name, the search continues with the class attributes. If a class attribute is found that is a @@ -1038,10 +1047,10 @@ the lookup. .. index:: triple: class instance; attribute; assignment -Attribute assignments and deletions update the instance's dictionary, never a -class's dictionary. If the class has a :meth:`~object.__setattr__` or -:meth:`~object.__delattr__` method, this is called instead of updating the instance -dictionary directly. +Attribute assignments and deletions update the instance's attribute mapping, +never a class's attribute mapping. If the class has a +:meth:`~object.__setattr__` or :meth:`~object.__delattr__` method, this is +called instead of updating the instance attribute mapping directly. .. index:: pair: object; numeric @@ -1055,9 +1064,13 @@ methods with certain special names. See section :ref:`specialnames`. single: __dict__ (instance attribute) single: __class__ (instance attribute) -Special attributes: :attr:`~object.__dict__` is the attribute dictionary; +Special attributes: :attr:`~object.__dict__` is the attribute mapping; :attr:`~instance.__class__` is the instance's class. +.. versionchanged:: 3.14 + + The :attr:`~object.__dict__` attribute may now be a mapping. + I/O objects (also known as file objects) ---------------------------------------- @@ -1109,7 +1122,7 @@ Code objects Code objects represent *byte-compiled* executable Python code, or :term:`bytecode`. The difference between a code object and a function object is that the function -object contains an explicit reference to the function's globals (the module in +object contains an explicit reference to the function's mapping (the module in which it was defined), while a code object contains no context; also the default argument values are stored in the function object, not in the code object (because they represent values calculated at run-time). Unlike function @@ -1356,11 +1369,14 @@ Special read-only attributes Return a proxy for optimized scopes. * - .. attribute:: frame.f_globals - - The dictionary used by the frame to look up + - The mapping used by the frame to look up :ref:`global variables ` + .. versionchanged:: 3.14 + The :attr:`~frame.f_globals` attribute may now be a mapping. + * - .. attribute:: frame.f_builtins - - The dictionary used by the frame to look up + - The mapping used by the frame to look up :ref:`built-in (intrinsic) names ` * - .. attribute:: frame.f_lasti @@ -1958,8 +1974,8 @@ access (use of, assignment to, or deletion of ``x.name``) for class instances. :meth:`__getattr__` and :meth:`__setattr__`.) This is done both for efficiency reasons and because otherwise :meth:`__getattr__` would have no way to access other attributes of the instance. Note that at least for instance variables, - you can take total control by not inserting any values in the instance attribute - dictionary (but instead inserting them in another object). See the + you can take total control by not inserting any values in the instance + attribute mapping (but instead inserting them in another object). See the :meth:`__getattribute__` method below for a way to actually get total control over attribute access. @@ -1992,8 +2008,9 @@ access (use of, assignment to, or deletion of ``x.name``) for class instances. .. method:: object.__setattr__(self, name, value) Called when an attribute assignment is attempted. This is called instead of - the normal mechanism (i.e. store the value in the instance dictionary). - *name* is the attribute name, *value* is the value to be assigned to it. + the normal mechanism (i.e. store the value in the instance attribute + mapping). *name* is the attribute name, *value* is the value to be assigned + to it. If :meth:`__setattr__` wants to assign to an instance attribute, it should call the base class method with the same name, for example, @@ -2066,7 +2083,7 @@ a module object to a subclass of :class:`types.ModuleType`. For example:: Defining module ``__getattr__`` and setting module ``__class__`` only affect lookups made using the attribute access syntax -- directly accessing the module globals (whether by code within the module, or via a reference - to the module's globals dictionary) is unaffected. + to the module's globals mapping) is unaffected. .. versionchanged:: 3.5 ``__class__`` module attribute is now writable. @@ -2087,10 +2104,10 @@ Implementing Descriptors The following methods only apply when an instance of the class containing the method (a so-called *descriptor* class) appears in an *owner* class (the -descriptor must be in either the owner's class dictionary or in the class -dictionary for one of its parents). In the examples below, "the attribute" -refers to the attribute whose name is the key of the property in the owner -class' :attr:`~object.__dict__`. +descriptor must be in either the owner's class attribute mapping or in the +class attribute mapping for one of its parents). In the examples below, "the +attribute" refers to the attribute whose name is the key of the property in the +owner class' :attr:`~object.__dict__`. .. method:: object.__get__(self, instance, owner=None) @@ -2149,9 +2166,9 @@ protocol: :meth:`~object.__get__`, :meth:`~object.__set__`, and those methods are defined for an object, it is said to be a descriptor. The default behavior for attribute access is to get, set, or delete the -attribute from an object's dictionary. For instance, ``a.x`` has a lookup chain -starting with ``a.__dict__['x']``, then ``type(a).__dict__['x']``, and -continuing through the base classes of ``type(a)`` excluding metaclasses. +attribute from an object's attribute mapping. For instance, ``a.x`` has a +lookup chain starting with ``a.__dict__['x']``, then ``type(a).__dict__['x']``, +and continuing through the base classes of ``type(a)`` excluding metaclasses. However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor @@ -2214,17 +2231,16 @@ Super Binding For instance bindings, the precedence of descriptor invocation depends on which descriptor methods are defined. A descriptor can define any combination of :meth:`~object.__get__`, :meth:`~object.__set__` and -:meth:`~object.__delete__`. If it does not -define :meth:`!__get__`, then accessing the attribute will return the descriptor -object itself unless there is a value in the object's instance dictionary. If -the descriptor defines :meth:`!__set__` and/or :meth:`!__delete__`, it is a data -descriptor; if it defines neither, it is a non-data descriptor. Normally, data -descriptors define both :meth:`!__get__` and :meth:`!__set__`, while non-data -descriptors have just the :meth:`!__get__` method. Data descriptors with -:meth:`!__get__` and :meth:`!__set__` (and/or :meth:`!__delete__`) defined -always override a redefinition in an -instance dictionary. In contrast, non-data descriptors can be overridden by -instances. +:meth:`~object.__delete__`. If it does not define :meth:`!__get__`, then +accessing the attribute will return the descriptor object itself unless there +is a value in the object's instance attribute mapping. If the descriptor +defines :meth:`!__set__` and/or :meth:`!__delete__`, it is a data descriptor; +if it defines neither, it is a non-data descriptor. Normally, data descriptors +define both :meth:`!__get__` and :meth:`!__set__`, while non-data descriptors +have just the :meth:`!__get__` method. Data descriptors with :meth:`!__get__` +and :meth:`!__set__` (and/or :meth:`!__delete__`) defined always override a +redefinition in an instance attribute mapping. In contrast, non-data +descriptors can be overridden by instances. Python methods (including those decorated with :func:`@staticmethod ` and :func:`@classmethod `) are @@ -2917,8 +2933,8 @@ through the object's keys; for sequences, it should iterate through the values. .. method:: object.__missing__(self, key) - Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` for dict subclasses - when key is not in the dictionary. + Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` + for dict subclasses when key is not in the dictionary. .. method:: object.__iter__(self) @@ -3263,8 +3279,8 @@ Special method lookup For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object's type, not in the object's instance -dictionary. That behaviour is the reason why the following code raises an -exception:: +attribute mapping. That behaviour is the reason why the following code raises +an exception:: >>> class C: ... pass diff --git a/Include/pymacro.h b/Include/pymacro.h index a7945ef84a46fc..dd588bdb6fa6f2 100644 --- a/Include/pymacro.h +++ b/Include/pymacro.h @@ -190,4 +190,58 @@ // "comparison of unsigned expression in '< 0' is always false". #define _Py_IS_TYPE_SIGNED(type) ((type)(-1) <= 0) +// Test if dict_or_mapping is a dict, and call dict_func if it is, or call +// mapping_func if not. Store the returning value in result. +#define _Py_DICT_OR_MAPPING(dict_or_mapping, result, dict_func, mapping_func, \ + ...) \ + if (PyDict_Check(dict_or_mapping)) { \ + result = dict_func(dict_or_mapping, ##__VA_ARGS__); \ + } \ + else { \ + result = mapping_func(dict_or_mapping, ##__VA_ARGS__); \ + } + +// Test if dict_or_mapping is a dict, and call dict_func if it is, or call +// mapping_func if dict_or_mapping is a mapping; otherwise execute else_block. +// Store the returning value in result. +#define _Py_DICT_OR_MAPPING_ELSE(dict_or_mapping, result, dict_func, \ + mapping_func, else_block, ...) \ + if (PyDict_Check(dict_or_mapping)) { \ + result = dict_func(dict_or_mapping, ##__VA_ARGS__); \ + } \ + else if (PyMapping_Check(dict_or_mapping)) { \ + result = mapping_func(dict_or_mapping, ##__VA_ARGS__); \ + } \ + else else_block + +// Unified dict/mapping API +#define _Py_DICT_OR_MAPPING_GETITEMREF(dict_or_mapping, obj, ref, result) \ + _Py_DICT_OR_MAPPING(dict_or_mapping, result, PyDict_GetItemRef, \ + PyMapping_GetOptionalItem, obj, ref) + +#define _Py_DICT_OR_MAPPING_GETITEMREF_ELSE(dict_or_mapping, obj, ref, \ + result, else_block) \ + _Py_DICT_OR_MAPPING_ELSE(dict_or_mapping, result, PyDict_GetItemRef, \ + PyMapping_GetOptionalItem, else_block, obj, ref) + +#define _Py_DICT_OR_MAPPING_SETITEM(dict_or_mapping, obj, ref, result) \ + _Py_DICT_OR_MAPPING(dict_or_mapping, result, PyDict_SetItem, \ + PyObject_SetItem, obj, ref) + +#define _Py_DICT_OR_MAPPING_DELITEM(dict_or_mapping, obj, result) \ + _Py_DICT_OR_MAPPING(dict_or_mapping, result, PyDict_DelItem, \ + PyMapping_DelItem, obj) + +#define _Py_DICT_OR_MAPPING_CONTAINS(dict_or_mapping, obj, result) \ + _Py_DICT_OR_MAPPING(dict_or_mapping, result, PyDict_Contains, \ + PyMapping_HasKeyWithError, obj) + +#define _Py_DICT_OR_MAPPING_CONTAINS_ELSE(dict_or_mapping, obj, result, \ + else_block) \ + _Py_DICT_OR_MAPPING_ELSE(dict_or_mapping, result, PyDict_Contains, \ + PyMapping_HasKeyWithError, else_block, obj) + +#define _Py_DICT_OR_MAPPING_KEYS(dict_or_mapping, result) \ + _Py_DICT_OR_MAPPING(dict_or_mapping, result, PyDict_Keys, PyMapping_Keys) + #endif /* Py_PYMACRO_H */ diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 5818e96d61f480..7b386dee47c5ff 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -572,6 +572,12 @@ class Foo(types.ModuleType): f = Foo("foo") self.assertRaises(TypeError, dir, f) + # dir(module_with_mapping__dict__) + class Foo(types.ModuleType): + __dict__ = collections.UserDict(x=7) + f = Foo("foo") + self.assertIn("x", dir(f)) + # dir(type) self.assertIn("strip", dir(str)) self.assertNotIn("__mro__", dir(str)) diff --git a/Lib/test/test_capi/test_eval_code_ex.py b/Lib/test/test_capi/test_eval_code_ex.py index b298e5007e5e7d..92ba9524ca1df5 100644 --- a/Lib/test/test_capi/test_eval_code_ex.py +++ b/Lib/test/test_capi/test_eval_code_ex.py @@ -21,11 +21,11 @@ def f(): eval_code_ex = _testcapi.eval_code_ex code = f.__code__ self.assertEqual(eval_code_ex(code, dict(a=1)), 1) + self.assertEqual(eval_code_ex(code, UserDict(a=1)), 1) self.assertRaises(NameError, eval_code_ex, code, {}) - self.assertRaises(SystemError, eval_code_ex, code, UserDict(a=1)) - self.assertRaises(SystemError, eval_code_ex, code, []) - self.assertRaises(SystemError, eval_code_ex, code, 1) + self.assertRaises(TypeError, eval_code_ex, code, []) + self.assertRaises(TypeError, eval_code_ex, code, 1) # CRASHES eval_code_ex(code, NULL) # CRASHES eval_code_ex(1, {}) # CRASHES eval_code_ex(NULL, {}) diff --git a/Lib/test/test_capi/test_run.py b/Lib/test/test_capi/test_run.py index 894f66b437a39c..db2fd40c4dd969 100644 --- a/Lib/test/test_capi/test_run.py +++ b/Lib/test/test_capi/test_run.py @@ -64,9 +64,9 @@ def run(s, *args): self.assertRaises(SystemError, run, b'a\n', NULL) self.assertRaises(SystemError, run, b'a\n', NULL, {}) self.assertRaises(SystemError, run, b'a\n', NULL, dict(a=1)) - self.assertRaises(SystemError, run, b'a\n', UserDict()) - self.assertRaises(SystemError, run, b'a\n', UserDict(), {}) - self.assertRaises(SystemError, run, b'a\n', UserDict(), dict(a=1)) + self.assertRaises(NameError, run, b'a\n', UserDict()) + self.assertRaises(NameError, run, b'a\n', UserDict(), {}) + self.assertIsNone(run(b'a\n', UserDict(), dict(a=1))) # CRASHES run(NULL, {}) @@ -97,9 +97,9 @@ def run(*args): self.assertRaises(SystemError, run, NULL) self.assertRaises(SystemError, run, NULL, {}) self.assertRaises(SystemError, run, NULL, dict(a=1)) - self.assertRaises(SystemError, run, UserDict()) - self.assertRaises(SystemError, run, UserDict(), {}) - self.assertRaises(SystemError, run, UserDict(), dict(a=1)) + self.assertRaises(NameError, run, UserDict()) + self.assertRaises(NameError, run, UserDict(), {}) + self.assertIsNone(run(UserDict(), dict(a=1))) @unittest.skipUnless(TESTFN_UNDECODABLE, 'only works if there are undecodable paths') @unittest.skipIf(os.name == 'nt', 'does not work on Windows') diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 9def47e101b496..361b518d94cea6 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -97,7 +97,7 @@ def keys(self): self.assertEqual(m.results, ('z', g)) exec('z = locals()', g, m) self.assertEqual(m.results, ('z', m)) - self.assertRaises(TypeError, exec, 'z = b', m) + self.assertRaises(NameError, exec, 'z = b', m) class A: "Non-mapping" diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index b3fc5ad42e7fde..993b1d15d7a89b 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -351,25 +351,24 @@ def test_unset_attr(self): class FunctionDictsTest(FuncAttrsTest): def test_setting_dict_to_invalid(self): self.cannot_set_attr(self.b, '__dict__', None, TypeError) - from collections import UserDict - d = UserDict({'known_attr': 7}) - self.cannot_set_attr(self.fi.a.__func__, '__dict__', d, TypeError) def test_setting_dict_to_valid(self): - d = {'known_attr': 7} - self.b.__dict__ = d - # Test assignment - self.assertIs(d, self.b.__dict__) - # ... and on all the different ways of referencing the method's func - self.F.a.__dict__ = d - self.assertIs(d, self.fi.a.__func__.__dict__) - self.assertIs(d, self.fi.a.__dict__) - # Test value - self.assertEqual(self.b.known_attr, 7) - self.assertEqual(self.b.__dict__['known_attr'], 7) - # ... and again, on all the different method's names - self.assertEqual(self.fi.a.__func__.known_attr, 7) - self.assertEqual(self.fi.a.known_attr, 7) + from collections import UserDict + for d in {'known_attr': 7}, UserDict({'known_attr': 7}): + with self.subTest(dict=d): + self.b.__dict__ = d + # Test assignment + self.assertIs(d, self.b.__dict__) + # ... and on all the different ways of referencing the method's func + self.F.a.__dict__ = d + self.assertIs(d, self.fi.a.__func__.__dict__) + self.assertIs(d, self.fi.a.__dict__) + # Test value + self.assertEqual(self.b.known_attr, 7) + self.assertEqual(self.b.__dict__['known_attr'], 7) + # ... and again, on all the different method's names + self.assertEqual(self.fi.a.__func__.known_attr, 7) + self.assertEqual(self.fi.a.known_attr, 7) def test_delete___dict__(self): try: diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-07-05-16-24-14.gh-issue-121306.nUBgho.rst b/Misc/NEWS.d/next/Core and Builtins/2024-07-05-16-24-14.gh-issue-121306.nUBgho.rst new file mode 100644 index 00000000000000..33d79882071055 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-07-05-16-24-14.gh-issue-121306.nUBgho.rst @@ -0,0 +1,2 @@ +Allow a mapping to be the :attr:`~object.__dict__` attribute of any object. This allows a mapping to be the global namespace of a module. It also allows a mapping to be passed as the *globals* argument for the built-in functions :func:`exec` and :func:`eval`. +Patch by Ben Hsing diff --git a/Objects/clinic/funcobject.c.h b/Objects/clinic/funcobject.c.h index 8f20bda26438cf..6bafa606dc43f0 100644 --- a/Objects/clinic/funcobject.c.h +++ b/Objects/clinic/funcobject.c.h @@ -18,7 +18,7 @@ PyDoc_STRVAR(func_new__doc__, " code\n" " a code object\n" " globals\n" -" the globals dictionary\n" +" the globals mapping\n" " name\n" " a string that overrides the name from the code object\n" " argdefs\n" @@ -82,10 +82,6 @@ func_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) goto exit; } code = (PyCodeObject *)fastargs[0]; - if (!PyDict_Check(fastargs[1])) { - _PyArg_BadArgument("function", "argument 'globals'", "dict", fastargs[1]); - goto exit; - } globals = fastargs[1]; if (!noptargs) { goto skip_optional_pos; @@ -115,4 +111,4 @@ func_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=10947342188f38a9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ea683473928f1d5d input=a9049054013a1b77]*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 2b11a01595b0bc..3f21e9f851eb76 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -7259,8 +7259,13 @@ _PyObjectDict_SetItem(PyTypeObject *tp, PyObject *obj, PyObject **dictptr, } Py_BEGIN_CRITICAL_SECTION(dict); - res = _PyDict_SetItem_LockHeld((PyDictObject *)dict, key, value); - ASSERT_CONSISTENT(dict); + if (PyDict_Check(dict)) { + res = _PyDict_SetItem_LockHeld((PyDictObject *)dict, key, value); + ASSERT_CONSISTENT(dict); + } + else { + res = PyObject_SetItem(dict, key, value); + } Py_END_CRITICAL_SECTION(); return res; } diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 3dc9ff058a5c9e..f4d93d8a570d81 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -2105,17 +2105,21 @@ PyFrame_GetGenerator(PyFrameObject *frame) PyObject* _PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals) { - PyObject *builtins = PyDict_GetItemWithError(globals, &_Py_ID(__builtins__)); - if (builtins) { - if (PyModule_Check(builtins)) { - builtins = _PyModule_GetDict(builtins); + PyObject *maybe_builtins; + int has_builtins; + _Py_DICT_OR_MAPPING_GETITEMREF(globals, &_Py_ID(__builtins__), + &maybe_builtins, has_builtins) + if (has_builtins < 0) { + return NULL; + } + if (has_builtins) { + if (PyModule_Check(maybe_builtins)) { + PyObject *builtins = Py_XNewRef(_PyModule_GetDict(maybe_builtins)); + Py_DECREF(maybe_builtins); assert(builtins != NULL); + return builtins; } - return builtins; + return maybe_builtins; } - if (PyErr_Occurred()) { - return NULL; - } - - return _PyEval_GetBuiltins(tstate); + return Py_NewRef(_PyEval_GetBuiltins(tstate)); } diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 40211297be20c0..d4ac13c3b0f5e2 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -103,7 +103,10 @@ PyFunctionObject * _PyFunction_FromConstructor(PyFrameConstructor *constr) { PyObject *module; - if (PyDict_GetItemRef(constr->fc_globals, &_Py_ID(__name__), &module) < 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF(constr->fc_globals, &_Py_ID(__name__), + &module, r) + if (r < 0) { return NULL; } @@ -141,7 +144,6 @@ PyObject * PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname) { assert(globals != NULL); - assert(PyDict_Check(globals)); Py_INCREF(globals); PyThreadState *tstate = _PyThreadState_GET(); @@ -174,15 +176,20 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname // __module__: Use globals['__name__'] if it exists, or NULL. PyObject *module; PyObject *builtins = NULL; - if (PyDict_GetItemRef(globals, &_Py_ID(__name__), &module) < 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF_ELSE(globals, &_Py_ID(__name__), &module, r, + { + goto error; + } + ) + if (r < 0) { goto error; } - builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref + builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); if (builtins == NULL) { goto error; } - Py_INCREF(builtins); PyFunctionObject *op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type); if (op == NULL) { @@ -922,8 +929,8 @@ class function "PyFunctionObject *" "&PyFunction_Type" function.__new__ as func_new code: object(type="PyCodeObject *", subclass_of="&PyCode_Type") a code object - globals: object(subclass_of="&PyDict_Type") - the globals dictionary + globals: object + the globals mapping name: object = None a string that overrides the name from the code object argdefs as defaults: object = None @@ -940,11 +947,16 @@ static PyObject * func_new_impl(PyTypeObject *type, PyCodeObject *code, PyObject *globals, PyObject *name, PyObject *defaults, PyObject *closure, PyObject *kwdefaults) -/*[clinic end generated code: output=de72f4c22ac57144 input=20c9c9f04ad2d3f2]*/ +/*[clinic end generated code: output=de72f4c22ac57144 input=d6fcf687f69bd689]*/ { PyFunctionObject *newfunc; Py_ssize_t nclosure; + if (!PyMapping_Check(globals)) { + PyErr_SetString(PyExc_TypeError, + "arg 2 (globals) must be a mapping"); + return NULL; + } if (name != Py_None && !PyUnicode_Check(name)) { PyErr_SetString(PyExc_TypeError, "arg 3 (name) must be None or string"); diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 73ad9711b6b0fc..ade3d6e4c90a68 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -62,16 +62,19 @@ module_init_dict(PyModuleObject *mod, PyObject *md_dict, if (doc == NULL) doc = Py_None; - if (PyDict_SetItem(md_dict, &_Py_ID(__name__), name) != 0) - return -1; - if (PyDict_SetItem(md_dict, &_Py_ID(__doc__), doc) != 0) - return -1; - if (PyDict_SetItem(md_dict, &_Py_ID(__package__), Py_None) != 0) - return -1; - if (PyDict_SetItem(md_dict, &_Py_ID(__loader__), Py_None) != 0) - return -1; - if (PyDict_SetItem(md_dict, &_Py_ID(__spec__), Py_None) != 0) - return -1; + int r; + +#define _Py_MODULE_INIT_SETATTR(name, value) \ + _Py_DICT_OR_MAPPING_SETITEM(md_dict, &_Py_ID(name), value, r) \ + if (r != 0) { \ + return -1; \ + } + + _Py_MODULE_INIT_SETATTR(__name__, name) + _Py_MODULE_INIT_SETATTR(__doc__, doc) + _Py_MODULE_INIT_SETATTR(__package__, Py_None) + _Py_MODULE_INIT_SETATTR(__loader__, Py_None) + _Py_MODULE_INIT_SETATTR(__spec__, Py_None) if (PyUnicode_CheckExact(name)) { Py_XSETREF(mod->md_name, Py_NewRef(name)); } @@ -566,11 +569,13 @@ PyModule_GetNameObject(PyObject *mod) return NULL; } PyObject *dict = ((PyModuleObject *)mod)->md_dict; // borrowed reference - if (dict == NULL || !PyDict_Check(dict)) { + if (dict == NULL || !PyMapping_Check(dict)) { goto error; } PyObject *name; - if (PyDict_GetItemRef(dict, &_Py_ID(__name__), &name) <= 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF(dict, &_Py_ID(__name__), &name, r) + if (r <= 0) { // error or not found goto error; } @@ -611,7 +616,9 @@ PyModule_GetFilenameObject(PyObject *mod) goto error; } PyObject *fileobj; - if (PyDict_GetItemRef(dict, &_Py_ID(__file__), &fileobj) <= 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF(dict, &_Py_ID(__file__), &fileobj, r) + if (r <= 0) { // error or not found goto error; } @@ -672,57 +679,73 @@ _PyModule_Clear(PyObject *m) void _PyModule_ClearDict(PyObject *d) { - /* To make the execution order of destructors for global - objects a bit more predictable, we first zap all objects - whose name starts with a single underscore, before we clear - the entire dictionary. We zap them by replacing them with - None, rather than deleting them from the dictionary, to - avoid rehashing the dictionary (to some extent). */ + /* To make the execution order of destructors for global objects a bit + more predictable, we first zap all objects whose name starts with a + single underscore, before we clear the entire mapping. We zap them + by replacing them with None, rather than deleting them from the mapping, + to avoid rehashing the mapping (to some extent). */ Py_ssize_t pos; PyObject *key, *value; int verbose = _Py_GetConfig()->verbose; - /* First, clear only names starting with a single underscore */ - pos = 0; - while (PyDict_Next(d, &pos, &key, &value)) { - if (value != Py_None && PyUnicode_Check(key)) { - if (PyUnicode_READ_CHAR(key, 0) == '_' && - PyUnicode_READ_CHAR(key, 1) != '_') { - if (verbose > 1) { - const char *s = PyUnicode_AsUTF8(key); - if (s != NULL) - PySys_WriteStderr("# clear[1] %s\n", s); - else - PyErr_Clear(); - } - if (PyDict_SetItem(d, key, Py_None) != 0) { - PyErr_FormatUnraisable("Exception ignored on clearing module dict"); - } - } - } +/* Define macros to DRY up 4 very similar blocks of code by type and filter */ +#define _Py_MODULE_CLEARDICT_FOR_DICT(action_block) \ + pos = 0; \ + while (PyDict_Next(d, &pos, &key, &value)) { \ + action_block \ + } + +#define _Py_MODULE_CLEARDICT_FOR_MAPPING(action_block) \ + for (pos = 0; pos < size; pos++) { \ + PyObject *key_value = PyList_GET_ITEM(items, pos); \ + key = PyTuple_GET_ITEM(key_value, 0); \ + value = PyTuple_GET_ITEM(key_value, 1); \ + action_block \ + } + +#define _Py_MODULE_CLEARDICT_BY_FILTER(phase, filter) \ + if (value != Py_None && PyUnicode_Check(key)) { \ + if (filter) { \ + if (verbose > 1) { \ + const char *s = PyUnicode_AsUTF8(key); \ + if (s != NULL) { \ + PySys_WriteStderr("# clear[" #phase "] %s\n", s); \ + } \ + else { \ + PyErr_Clear(); \ + } \ + } \ + int r; \ + _Py_DICT_OR_MAPPING_SETITEM(d, key, Py_None, r) \ + if (r != 0) { \ + PyErr_FormatUnraisable( \ + "Exception ignored on clearing module dict"); \ + } \ + } \ + } + +#define _Py_MODULE_CLEARDICT_OF_TYPE(type) \ + /* First, clear only names starting with a single underscore */ \ + _Py_MODULE_CLEARDICT_FOR_##type( \ + _Py_MODULE_CLEARDICT_BY_FILTER(1, \ + PyUnicode_READ_CHAR(key, 0) == '_' && \ + PyUnicode_READ_CHAR(key, 1) != '_')) \ + /* Next, clear all names except for __builtins__ */ \ + _Py_MODULE_CLEARDICT_FOR_##type( \ + _Py_MODULE_CLEARDICT_BY_FILTER(2, \ + PyUnicode_READ_CHAR(key, 0) != '_' || \ + !_PyUnicode_EqualToASCIIString(key, "__builtins__"))) + + if (PyDict_Check(d)) { + _Py_MODULE_CLEARDICT_OF_TYPE(DICT) } - - /* Next, clear all names except for __builtins__ */ - pos = 0; - while (PyDict_Next(d, &pos, &key, &value)) { - if (value != Py_None && PyUnicode_Check(key)) { - if (PyUnicode_READ_CHAR(key, 0) != '_' || - !_PyUnicode_EqualToASCIIString(key, "__builtins__")) - { - if (verbose > 1) { - const char *s = PyUnicode_AsUTF8(key); - if (s != NULL) - PySys_WriteStderr("# clear[2] %s\n", s); - else - PyErr_Clear(); - } - if (PyDict_SetItem(d, key, Py_None) != 0) { - PyErr_FormatUnraisable("Exception ignored on clearing module dict"); - } - } - } + else { + PyObject *items = PyMapping_Items(d); + Py_ssize_t size = PyList_Size(items); + _Py_MODULE_CLEARDICT_OF_TYPE(MAPPING) + Py_DECREF(items); } /* Note: we leave __builtins__ in place, so that destructors @@ -940,7 +963,9 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress) PyErr_Clear(); } assert(m->md_dict != NULL); - if (PyDict_GetItemRef(m->md_dict, &_Py_ID(__getattr__), &getattr) < 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF(m->md_dict, &_Py_ID(__getattr__), &getattr, r) + if (r < 0) { return NULL; } if (getattr) { @@ -958,7 +983,8 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress) if (suppress == 1) { return NULL; } - if (PyDict_GetItemRef(m->md_dict, &_Py_ID(__name__), &mod_name) < 0) { + _Py_DICT_OR_MAPPING_GETITEMREF(m->md_dict, &_Py_ID(__name__), &mod_name, r) + if (r < 0) { return NULL; } if (!mod_name || !PyUnicode_Check(mod_name)) { @@ -968,7 +994,8 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress) return NULL; } PyObject *spec; - if (PyDict_GetItemRef(m->md_dict, &_Py_ID(__spec__), &spec) < 0) { + _Py_DICT_OR_MAPPING_GETITEMREF(m->md_dict, &_Py_ID(__spec__), &spec, r) + if (r < 0) { Py_DECREF(mod_name); return NULL; } @@ -1109,17 +1136,20 @@ module_dir(PyObject *self, PyObject *args) PyObject *dict = PyObject_GetAttr(self, &_Py_ID(__dict__)); if (dict != NULL) { - if (PyDict_Check(dict)) { - PyObject *dirfunc = PyDict_GetItemWithError(dict, &_Py_ID(__dir__)); - if (dirfunc) { - result = _PyObject_CallNoArgs(dirfunc); - } - else if (!PyErr_Occurred()) { - result = PyDict_Keys(dict); + PyObject *dirfunc = NULL; + int r = -1; + _Py_DICT_OR_MAPPING_GETITEMREF_ELSE(dict, &_Py_ID(__dir__), &dirfunc, r, + { + PyErr_Format(PyExc_TypeError, + ".__dict__ is not a mapping"); } + ) + if (dirfunc) { + result = _PyObject_CallNoArgs(dirfunc); + Py_DECREF(dirfunc); } - else { - PyErr_Format(PyExc_TypeError, ".__dict__ is not a dictionary"); + else if (r == 0) { + _Py_DICT_OR_MAPPING_KEYS(dict, result) } } @@ -1140,8 +1170,8 @@ module_get_dict(PyModuleObject *m) if (dict == NULL) { return NULL; } - if (!PyDict_Check(dict)) { - PyErr_Format(PyExc_TypeError, ".__dict__ is not a dictionary"); + if (!PyMapping_Check(dict)) { + PyErr_Format(PyExc_TypeError, ".__dict__ is not a mapping"); Py_DECREF(dict); return NULL; } @@ -1157,9 +1187,12 @@ module_get_annotate(PyModuleObject *m, void *Py_UNUSED(ignored)) } PyObject *annotate; - if (PyDict_GetItemRef(dict, &_Py_ID(__annotate__), &annotate) == 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF(dict, &_Py_ID(__annotate__), &annotate, r) + if (r == 0) { annotate = Py_None; - if (PyDict_SetItem(dict, &_Py_ID(__annotate__), annotate) == -1) { + _Py_DICT_OR_MAPPING_SETITEM(dict, &_Py_ID(__annotate__), annotate, r) + if (r == -1) { Py_CLEAR(annotate); } } @@ -1185,14 +1218,20 @@ module_set_annotate(PyModuleObject *m, PyObject *value, void *Py_UNUSED(ignored) return -1; } - if (PyDict_SetItem(dict, &_Py_ID(__annotate__), value) == -1) { + int r; + _Py_DICT_OR_MAPPING_SETITEM(dict, &_Py_ID(__annotate__), value, r) + if (r == -1) { Py_DECREF(dict); return -1; } if (!Py_IsNone(value)) { - if (PyDict_Pop(dict, &_Py_ID(__annotations__), NULL) == -1) { - Py_DECREF(dict); - return -1; + _Py_DICT_OR_MAPPING_DELITEM(dict, &_Py_ID(__annotations__), r) + if (r == -1) { + if (!PyErr_ExceptionMatches(PyExc_KeyError)) { + Py_DECREF(dict); + return -1; + } + PyErr_Clear(); } } Py_DECREF(dict); @@ -1208,9 +1247,14 @@ module_get_annotations(PyModuleObject *m, void *Py_UNUSED(ignored)) } PyObject *annotations; - if (PyDict_GetItemRef(dict, &_Py_ID(__annotations__), &annotations) == 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF(dict, &_Py_ID(__annotations__), + &annotations, r) + if (r == 0) { PyObject *annotate; - int annotate_result = PyDict_GetItemRef(dict, &_Py_ID(__annotate__), &annotate); + int annotate_result; + _Py_DICT_OR_MAPPING_GETITEMREF(dict, &_Py_ID(__annotate__), + &annotate, annotate_result) if (annotate_result < 0) { Py_DECREF(dict); return NULL; @@ -1224,7 +1268,8 @@ module_get_annotations(PyModuleObject *m, void *Py_UNUSED(ignored)) return NULL; } if (!PyDict_Check(annotations)) { - PyErr_Format(PyExc_TypeError, "__annotate__ returned non-dict of type '%.100s'", + PyErr_Format(PyExc_TypeError, + "__annotate__ returned non-dict of type '%.100s'", Py_TYPE(annotations)->tp_name); Py_DECREF(annotate); Py_DECREF(annotations); @@ -1237,8 +1282,9 @@ module_get_annotations(PyModuleObject *m, void *Py_UNUSED(ignored)) } Py_XDECREF(annotate); if (annotations) { - int result = PyDict_SetItem( - dict, &_Py_ID(__annotations__), annotations); + int result; + _Py_DICT_OR_MAPPING_SETITEM(dict, &_Py_ID(__annotations__), + annotations, result) if (result) { Py_CLEAR(annotations); } @@ -1259,21 +1305,29 @@ module_set_annotations(PyModuleObject *m, PyObject *value, void *Py_UNUSED(ignor if (value != NULL) { /* set */ - ret = PyDict_SetItem(dict, &_Py_ID(__annotations__), value); + _Py_DICT_OR_MAPPING_SETITEM(dict, &_Py_ID(__annotations__), value, ret) } else { /* delete */ - ret = PyDict_Pop(dict, &_Py_ID(__annotations__), NULL); - if (ret == 0) { + _Py_DICT_OR_MAPPING_DELITEM(dict, &_Py_ID(__annotations__), ret) + if (ret == -1 && PyErr_ExceptionMatches(PyExc_KeyError)) { PyErr_SetObject(PyExc_AttributeError, &_Py_ID(__annotations__)); - ret = -1; } else if (ret > 0) { ret = 0; } } - if (ret == 0 && PyDict_Pop(dict, &_Py_ID(__annotate__), NULL) < 0) { - ret = -1; + if (ret == 0) { + int r; + _Py_DICT_OR_MAPPING_DELITEM(dict, &_Py_ID(__annotate__), r) + if (r == -1) { + if (PyErr_ExceptionMatches(PyExc_KeyError)) { + PyErr_Clear(); + } + else { + ret = -1; + } + } } Py_DECREF(dict); diff --git a/Objects/object.c b/Objects/object.c index c4622359bb1035..1c34a724b422ed 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1498,7 +1498,9 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) } if (dict != NULL) { Py_INCREF(dict); - if (PyDict_GetItemRef(dict, name, method) != 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF(dict, name, method, r) + if (r != 0) { // found or error Py_DECREF(dict); Py_XDECREF(descr); @@ -1604,7 +1606,8 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, } if (dict != NULL) { Py_INCREF(dict); - int rc = PyDict_GetItemRef(dict, name, &res); + int rc; + _Py_DICT_OR_MAPPING_GETITEMREF(dict, name, &res, rc) Py_DECREF(dict); if (res != NULL) { goto done; @@ -1729,10 +1732,12 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name, } else { Py_INCREF(dict); - if (value == NULL) - res = PyDict_DelItem(dict, name); - else - res = PyDict_SetItem(dict, name, value); + if (value == NULL) { + _Py_DICT_OR_MAPPING_DELITEM(dict, name, res) + } + else { + _Py_DICT_OR_MAPPING_SETITEM(dict, name, value, res) + } Py_DECREF(dict); } error_check: @@ -1776,9 +1781,9 @@ PyObject_GenericSetDict(PyObject *obj, PyObject *value, void *context) PyErr_SetString(PyExc_TypeError, "cannot delete __dict__"); return -1; } - if (!PyDict_Check(value)) { + if (!PyMapping_Check(value)) { PyErr_Format(PyExc_TypeError, - "__dict__ must be set to a dictionary, " + "__dict__ must be set to a mapping, " "not a '%.200s'", Py_TYPE(value)->tp_name); return -1; } diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index a5b45e358d9efb..56f12ed209c56b 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -960,13 +960,9 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, const char *str; if (locals != Py_None && !PyMapping_Check(locals)) { - PyErr_SetString(PyExc_TypeError, "locals must be a mapping"); - return NULL; - } - if (globals != Py_None && !PyDict_Check(globals)) { - PyErr_SetString(PyExc_TypeError, PyMapping_Check(globals) ? - "globals must be a real dict; try eval(expr, {}, mapping)" - : "globals must be a dict"); + PyErr_Format(PyExc_TypeError, + "locals must be a mapping or None, not %.100s", + Py_TYPE(locals)->tp_name); return NULL; } if (globals == Py_None) { @@ -993,13 +989,25 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, goto error; } - int r = PyDict_Contains(globals, &_Py_ID(__builtins__)); - if (r == 0) { - r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins()); - } + int r; + _Py_DICT_OR_MAPPING_CONTAINS_ELSE(globals, &_Py_ID(__builtins__), r, + { + PyErr_Format(PyExc_TypeError, + "globals must be a mapping or None, not %.100s", + Py_TYPE(globals)->tp_name); + return NULL; + } + ) if (r < 0) { goto error; } + if (r == 0) { + _Py_DICT_OR_MAPPING_SETITEM(globals, &_Py_ID(__builtins__), + PyEval_GetBuiltins(), r) + if (r < 0) { + goto error; + } + } if (PyCode_Check(source)) { if (PySys_Audit("exec", "O", source) < 0) { @@ -1084,24 +1092,32 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, Py_INCREF(locals); } - if (!PyDict_Check(globals)) { - PyErr_Format(PyExc_TypeError, "exec() globals must be a dict, not %.100s", - Py_TYPE(globals)->tp_name); - goto error; - } if (!PyMapping_Check(locals)) { PyErr_Format(PyExc_TypeError, "locals must be a mapping or None, not %.100s", Py_TYPE(locals)->tp_name); goto error; } - int r = PyDict_Contains(globals, &_Py_ID(__builtins__)); - if (r == 0) { - r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins()); - } + + int r; + _Py_DICT_OR_MAPPING_CONTAINS_ELSE(globals, &_Py_ID(__builtins__), r, + { + PyErr_Format(PyExc_TypeError, + "globals must be a mapping or None, not %.100s", + Py_TYPE(globals)->tp_name); + goto error; + } + ) if (r < 0) { goto error; } + if (r == 0) { + _Py_DICT_OR_MAPPING_SETITEM(globals, &_Py_ID(__builtins__), + PyEval_GetBuiltins(), r) + if (r < 0) { + goto error; + } + } if (closure == Py_None) { closure = NULL; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 76587a4f0dc695..329d058b37d87a 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1465,22 +1465,27 @@ dummy_func( } inst(STORE_GLOBAL, (v --)) { + PyObject *globals = GLOBALS(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); - int err = PyDict_SetItem(GLOBALS(), name, PyStackRef_AsPyObjectBorrow(v)); + int err; + _Py_DICT_OR_MAPPING_SETITEM(globals, name, + PyStackRef_AsPyObjectBorrow(v), err) DECREF_INPUTS(); ERROR_IF(err, error); } inst(DELETE_GLOBAL, (--)) { + PyObject *globals = GLOBALS(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); - int err = PyDict_Pop(GLOBALS(), name, NULL); + int err; + _Py_DICT_OR_MAPPING_DELITEM(globals, name, err) // Can't use ERROR_IF here. if (err < 0) { - ERROR_NO_POP(); - } - if (err == 0) { - _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + if (_PyErr_Occurred(tstate) && + _PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { + _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, + NAME_ERROR_MSG, name); + } ERROR_NO_POP(); } } @@ -1540,6 +1545,7 @@ dummy_func( inst(LOAD_NAME, (-- v)) { PyObject *v_o; + PyObject *globals = GLOBALS(); PyObject *mod_or_class_dict = LOCALS(); if (mod_or_class_dict == NULL) { _PyErr_SetString(tstate, PyExc_SystemError, @@ -1551,7 +1557,9 @@ dummy_func( ERROR_NO_POP(); } if (v_o == NULL) { - if (PyDict_GetItemRef(GLOBALS(), name, &v_o) < 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF(globals, name, &v_o, r) + if (r < 0) { ERROR_NO_POP(); } if (v_o == NULL) { diff --git a/Python/ceval.c b/Python/ceval.c index a240ed4321f7ee..448c0d973dbd6e 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -205,7 +205,8 @@ maybe_lltrace_resume_frame(_PyInterpreterFrame *frame, _PyInterpreterFrame *skip if (frame == skip_frame) { return 0; } - int r = PyDict_Contains(globals, &_Py_ID(__lltrace__)); + int r; + _Py_DICT_OR_MAPPING_CONTAINS(globals, &_Py_ID(__lltrace__), r) if (r < 0) { return -1; } @@ -602,7 +603,7 @@ PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals) if (locals == NULL) { locals = globals; } - PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref + PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); if (builtins == NULL) { return NULL; } @@ -623,6 +624,7 @@ PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals) EVAL_CALL_STAT_INC(EVAL_CALL_LEGACY); PyObject *res = _PyEval_Vector(tstate, func, locals, NULL, 0, NULL); Py_DECREF(func); + Py_DECREF(builtins); return res; } @@ -1890,7 +1892,7 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals, if (defaults == NULL) { return NULL; } - PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref + PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); if (builtins == NULL) { Py_DECREF(defaults); return NULL; @@ -1946,6 +1948,7 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals, Py_XDECREF(kwnames); PyMem_Free(newargs); Py_DECREF(defaults); + Py_DECREF(builtins); return res; } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 3b999465aac815..27144f58895127 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1509,8 +1509,11 @@ _PyStackRef v; oparg = CURRENT_OPARG(); v = stack_pointer[-1]; + PyObject *globals = GLOBALS(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); - int err = PyDict_SetItem(GLOBALS(), name, PyStackRef_AsPyObjectBorrow(v)); + int err; + _Py_DICT_OR_MAPPING_SETITEM(globals, name, + PyStackRef_AsPyObjectBorrow(v), err) PyStackRef_CLOSE(v); if (err) JUMP_TO_ERROR(); stack_pointer += -1; @@ -1520,15 +1523,17 @@ case _DELETE_GLOBAL: { oparg = CURRENT_OPARG(); + PyObject *globals = GLOBALS(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); - int err = PyDict_Pop(GLOBALS(), name, NULL); + int err; + _Py_DICT_OR_MAPPING_DELITEM(globals, name, err) // Can't use ERROR_IF here. if (err < 0) { - JUMP_TO_ERROR(); - } - if (err == 0) { - _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + if (_PyErr_Occurred(tstate) && + _PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { + _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, + NAME_ERROR_MSG, name); + } JUMP_TO_ERROR(); } break; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 61057221291c0a..ba0c723d00c869 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2777,15 +2777,17 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(DELETE_GLOBAL); + PyObject *globals = GLOBALS(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); - int err = PyDict_Pop(GLOBALS(), name, NULL); + int err; + _Py_DICT_OR_MAPPING_DELITEM(globals, name, err) // Can't use ERROR_IF here. if (err < 0) { - goto error; - } - if (err == 0) { - _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + if (_PyErr_Occurred(tstate) && + _PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { + _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, + NAME_ERROR_MSG, name); + } goto error; } DISPATCH(); @@ -4974,6 +4976,7 @@ INSTRUCTION_STATS(LOAD_NAME); _PyStackRef v; PyObject *v_o; + PyObject *globals = GLOBALS(); PyObject *mod_or_class_dict = LOCALS(); if (mod_or_class_dict == NULL) { _PyErr_SetString(tstate, PyExc_SystemError, @@ -4985,7 +4988,9 @@ goto error; } if (v_o == NULL) { - if (PyDict_GetItemRef(GLOBALS(), name, &v_o) < 0) { + int r; + _Py_DICT_OR_MAPPING_GETITEMREF(globals, name, &v_o, r) + if (r < 0) { goto error; } if (v_o == NULL) { @@ -6182,8 +6187,11 @@ INSTRUCTION_STATS(STORE_GLOBAL); _PyStackRef v; v = stack_pointer[-1]; + PyObject *globals = GLOBALS(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); - int err = PyDict_SetItem(GLOBALS(), name, PyStackRef_AsPyObjectBorrow(v)); + int err; + _Py_DICT_OR_MAPPING_SETITEM(globals, name, + PyStackRef_AsPyObjectBorrow(v), err) PyStackRef_CLOSE(v); if (err) goto pop_1_error; stack_pointer += -1; diff --git a/Python/pythonrun.c b/Python/pythonrun.c index ce7f194e929c9c..bc13afe2fda45b 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -387,7 +387,7 @@ set_main_loader(PyObject *d, PyObject *filename, const char *loader_name) return -1; } - if (PyDict_SetItemString(d, "__loader__", loader) < 0) { + if (PyDict_SetItem(d, &_Py_ID(__loader__), loader) < 0) { Py_DECREF(loader); return -1; } @@ -1273,18 +1273,23 @@ run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, Py _PyRuntime.signals.unhandled_keyboard_interrupt = 0; /* Set globals['__builtins__'] if it doesn't exist */ - if (!globals || !PyDict_Check(globals)) { - PyErr_SetString(PyExc_SystemError, "globals must be a real dict"); - return NULL; - } - int has_builtins = PyDict_ContainsString(globals, "__builtins__"); - if (has_builtins < 0) { - return NULL; + int has_builtins; + if (!globals) { + goto error; } - if (!has_builtins) { - if (PyDict_SetItemString(globals, "__builtins__", - tstate->interp->builtins) < 0) + else _Py_DICT_OR_MAPPING_CONTAINS_ELSE(globals, &_Py_ID(__builtins__), + has_builtins, { +error: + PyErr_SetString(PyExc_SystemError, "globals must be a mapping"); + return NULL; + } + ) + if (!has_builtins) { + int r; + _Py_DICT_OR_MAPPING_SETITEM(globals, &_Py_ID(__builtins__), + tstate->interp->builtins, r) + if (r < 0) { return NULL; } }