Skip to content

gh-121306: allow a mapping as __dict__ of object #121389

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

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
970e3d9
allow a mapping, rather than just a dict subclass, as globals
blhsing Jul 5, 2024
cf9945e
fixed typo
blhsing Jul 5, 2024
55f6dd4
regenerated header files
blhsing Jul 5, 2024
0d347f8
fixed error handling of DELETE_GLOBAL
blhsing Jul 5, 2024
4e1af59
fixed borrowed builtins reference
blhsing Jul 5, 2024
40f7c35
fixed error handling
blhsing Jul 5, 2024
698aa1f
use PyMapping_HasKeyStringWithError for proper error handling
blhsing Jul 5, 2024
fa1dcac
fixed usage of id __lltrace__
blhsing Jul 5, 2024
4343867
made _PyEval_BuiltinsFromGlobals return a new, non-borrowed reference
blhsing Jul 5, 2024
c5b38b0
release reference to builtins module
blhsing Jul 5, 2024
0daa66b
streamline code
blhsing Jul 5, 2024
2852b8f
added dict-specific path for insertion of __builtins__ into globals
blhsing Jul 5, 2024
5120260
try PyDict_* before PyMapping_*
blhsing Jul 5, 2024
723d665
switch from EAFP to LBYL
blhsing Jul 5, 2024
3908ee8
fixed typo
blhsing Jul 5, 2024
4a14682
dict-specific path for function
blhsing Jul 5, 2024
ab2974e
more dict-specific paths
blhsing Jul 5, 2024
1218ab7
minor adjustments
blhsing Jul 5, 2024
375af2b
📜🤖 Added by blurb_it.
blurb-it[bot] Jul 5, 2024
06b225b
removed redundant PyDict_Check; streamlined code
blhsing Jul 8, 2024
d584987
fixed check for null globals
blhsing Jul 8, 2024
3d3a563
updated docs
blhsing Jul 8, 2024
8bbc34c
minor update to docs
blhsing Jul 8, 2024
ca2ea12
updated tests
blhsing Jul 8, 2024
0f9e289
Update 2024-07-05-16-24-14.gh-issue-121306.nUBgho.rst
blhsing Jul 8, 2024
186ee55
Update 2024-07-05-16-24-14.gh-issue-121306.nUBgho.rst
blhsing Jul 8, 2024
190c09f
fixed reference count
blhsing Jul 8, 2024
09df171
Merge branch 'master' into allow-mapping-as-globals
blhsing Jul 8, 2024
1949be7
updated test to validate evaluated result
blhsing Jul 9, 2024
bb888ff
updated style
blhsing Jul 9, 2024
db01f51
regen headers
blhsing Jul 9, 2024
f0cd4e8
removed comments that are no longer applicable
blhsing Jul 9, 2024
d7d7d04
allow a mapping to be object.__dict__ and by extension module.__dict_…
blhsing Jul 11, 2024
cab9a52
simplified macros for unified dict/mapping API
blhsing Jul 11, 2024
f8c62c6
corrected comments
blhsing Jul 11, 2024
aa2d9b4
regen headers
blhsing Jul 11, 2024
362e0ac
fixed headers
blhsing Jul 11, 2024
bf7be59
simplified macro
blhsing Jul 11, 2024
0bf8239
fixed reference count
blhsing Jul 11, 2024
278c2ca
fixed typo in doc
blhsing Jul 11, 2024
d6735b9
fixed reference count
blhsing Jul 11, 2024
18dad2b
fixed possible uninitialized usage
blhsing Jul 11, 2024
35ada07
fixed logical operation
blhsing Jul 11, 2024
2e6a88a
updated tests
blhsing Jul 11, 2024
c29a10c
added test for mapping __dict__ for module; DRYer code with better ma…
blhsing Jul 12, 2024
3513a29
updated comment
blhsing Jul 12, 2024
97b813d
updated comment
blhsing Jul 12, 2024
6ea65b9
reformatted code; updated news
blhsing Jul 12, 2024
8137cab
reformatted code for clarity
blhsing Jul 12, 2024
725a327
fixed typo
blhsing Jul 12, 2024
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
33 changes: 20 additions & 13 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``).
Expand All @@ -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 <nested scope>` (non-locals) in the enclosing
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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::

Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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)

Expand Down
14 changes: 8 additions & 6 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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: ``<module 'sys'
(built-in)>``. If loaded from a file, they are written as ``<module 'os' from
Expand Down Expand Up @@ -5486,9 +5486,11 @@ types, where they are relevant. Some of these are not reported by the

.. attribute:: object.__dict__

A dictionary or other mapping object used to store an object's (writable)
attributes.
A mapping object used to store an object's (writable) attributes.

.. versionchanged:: 3.14

The :attr:`~object.__dict__` attribute may now be a mapping.

.. attribute:: instance.__class__

Expand Down
116 changes: 66 additions & 50 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ Special read-only attributes
- Meaning

* - .. attribute:: function.__globals__
- A reference to the :class:`dictionary <dict>` that holds the function's
- A reference to the :term:`mapping` that holds the function's
:ref:`global variables <naming>` -- the global namespace of the module
in which the function was defined.

Expand Down Expand Up @@ -868,15 +868,15 @@ the :ref:`import system <importsystem>` 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 <dict>` 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::
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
----------------------------------------
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <naming>`

.. 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 <naming>`

* - .. attribute:: frame.f_lasti
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <staticmethod>` and :func:`@classmethod <classmethod>`) are
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading