PEP 726: Update, new sections (#3425)
This commit is contained in:
parent
fa9f7201c0
commit
150fbe9368
|
@ -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
|
||||
=========
|
||||
|
||||
|
|
Loading…
Reference in New Issue