PEP 726: Update, new sections (#3425)

This commit is contained in:
Sergey B Kirpichev 2023-10-30 18:35:48 +03:00 committed by GitHub
parent fa9f7201c0
commit 150fbe9368
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 90 additions and 9 deletions

View File

@ -31,14 +31,12 @@ There are several potential uses of a module ``__setattr__``:
Proper support for read-only attributes would also require adding the
``__delattr__`` function to prevent their deletion.
A typical workaround is assigning the ``__class__`` of a module object to a
custom subclass of :py:class:`python:types.ModuleType` (see [1]_).
Unfortunately, this also brings a noticeable speed regression
(~2-3x) for attribute *access*. It would be convenient to directly
support such customization, by recognizing ``__setattr__`` and ``__delattr__``
methods defined in a module that would act like normal
:py:meth:`python:object.__setattr__` and :py:meth:`python:object.__delattr__`
methods, except that they will be defined on module *instances*.
It would be convenient to directly support such customization, by recognizing
``__setattr__`` and ``__delattr__`` methods defined in a module that would act
like normal :py:meth:`python:object.__setattr__` and
:py:meth:`python:object.__delattr__` methods, except that they will be defined
on module *instances*. Together with existing ``__getattr__`` and ``__dir__``
methods this will streamline all variants of customizing module attribute access.
For example
@ -110,6 +108,36 @@ For example
ValueError: non-negative integer expected
Existing Options
================
The current workaround is assigning the ``__class__`` of a module object to a
custom subclass of :py:class:`python:types.ModuleType` (see [1]_).
For example, to prevent modification or deletion of an attribute we could use:
.. code:: python
# mod.py
import sys
from types import ModuleType
CONSTANT = 3.14
class ImmutableModule(ModuleType):
def __setattr__(name, value):
raise AttributeError('Read-only attribute!')
def __delattr__(name):
raise AttributeError('Read-only attribute!')
sys.modules[__name__].__class__ = ImmutableModule
But this variant is slower (~2x) than the proposed solution. More
importantly, it also brings a noticeable speed regression (~2-3x) for
attribute *access*.
Specification
=============
@ -135,7 +163,7 @@ customize setting the attribute or its deletion, else the normal
mechanism (storing/deleting the value in the module dictionary) will work.
Defining module ``__setattr__`` or ``__delattr__`` only affects lookups made
using the attribute access syntax---directly accessing the module globals
using the attribute access syntax --- directly accessing the module globals
(whether by ``globals()`` within the module, or via a reference to the module's
globals dictionary) is unaffected. For example:
@ -176,6 +204,11 @@ one can access it via ``sys.modules[__name__]`` within the module's code:
sys.modules[__name__].bar = 'spam' # triggers __setattr__
This limitation is intentional (just as for the :pep:`562`), because the
interpreter highly optimizes access to module globals and disabling all that
and going through special methods written in Python would slow down the code
unacceptably.
How to Teach This
=================
@ -209,6 +242,54 @@ attribute access, which is much more likely scenario to get a
performance penalty.
Discussion
==========
As pointed out by Victor Stinner, the proposed API could be useful already in
the stdlib, for example to ensure that :py:obj:`sys.modules` type is always a
list:
.. code:: pycon
>>> import sys
>>> sys.modules = 123
>>> import asyncio
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<frozen importlib._bootstrap>", line 1260, in _find_and_load
AttributeError: 'int' object has no attribute 'get'
or to prevent deletion of critical :py:mod:`sys` attributes, which makes the
code more complicated. For example, code using :py:obj:`sys.stderr` has to
check if the attribute exists and if it's not :py:obj:`None`. Currently, it's
possible to remove any :py:mod:`sys` attribute, including functions:
.. code:: pycon
>>> import sys
>>> del sys.excepthook
>>> 1+ # notice the next line
sys.excepthook is missing
File "<stdin>", line 1
1+
^
SyntaxError: invalid syntax
See `related issue
<https://github.com/python/cpython/issues/106016#issue-1771174774>`__ for
other details.
Other stdlib modules also come with attributes which can be overriden (as a
feature) and some input validation here could be helpful. Examples:
:py:obj:`threading.excepthook`, :py:obj:`warnings.showwarning`,
:py:obj:`io.DEFAULT_BUFFER_SIZE` or :py:obj:`os.SEEK_SET`.
Also a typical use case for customizing module attribute access is managing
deprecation warnings. But the :pep:`562` accomplishes this scenario only
partially: e.g. it's impossible to issue a warning during an attempt to
*change* a renamed attribute.
Footnotes
=========