PEP 702: Runtime warnings by default (#3006)
This commit is contained in:
parent
f292166dba
commit
5f9a9e7f10
83
pep-0702.rst
83
pep-0702.rst
|
@ -16,7 +16,8 @@ Abstract
|
|||
========
|
||||
|
||||
This PEP adds an ``@typing.deprecated()`` decorator that marks a class or function
|
||||
as deprecated, enabling static checkers to warn when it is used.
|
||||
as deprecated, enabling static checkers to warn when it is used. By default, this
|
||||
decorator will also raise a runtime ``DeprecationWarning``.
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
@ -175,10 +176,30 @@ Here is how type checkers should handle usage of this library:
|
|||
Runtime behavior
|
||||
----------------
|
||||
|
||||
At runtime, the decorator sets an attribute ``__deprecated__`` on the decorated
|
||||
object. The value of the attribute is the message passed to the decorator.
|
||||
The decorator returns the original object. Notably, it does not issue a runtime
|
||||
:exc:`DeprecationWarning`.
|
||||
The ``@deprecated`` parameter takes two keyword-only arguments:
|
||||
|
||||
* ``category``: A warning class. Defaults to :exc:`DeprecationWarning`. If this
|
||||
is set to ``None``, no warning is issued at runtime and the decorator returns
|
||||
the original object, except for setting the ``__deprecated__`` attribute (see below).
|
||||
* ``stacklevel``: The number of stack frames to skip when issuing the warning.
|
||||
Defaults to 1, indicating that the warning should be issued at the site where the
|
||||
deprecated object is called. Internally, the implementation will add the number of
|
||||
stack frames it uses in wrapper code.
|
||||
|
||||
If the decorated object is a class, the decorator wraps the ``__new__`` methods
|
||||
such that instantiating the class issues a warning. If the decorated object is a
|
||||
callable, the decorator returns a new callable that wraps the original callable but
|
||||
raises a warning when called. Otherwise, the decorator raises a ``TypeError``
|
||||
(unless ``category=None`` is passed).
|
||||
|
||||
There are several scenarios where use of the decorated object cannot issue a warning,
|
||||
including overloads, ``Protocol`` classes, and abstract methods. Type checkers may show a
|
||||
warning if ``@deprecated`` is used without ``category=None`` in these cases.
|
||||
|
||||
To accommodate runtime introspection, the decorator sets an attribute
|
||||
``__deprecated__`` on the object it is passed, as well as on the wrapper
|
||||
callables it generates for deprecated classes and functions.
|
||||
The value of the attribute is the message passed to the decorator.
|
||||
|
||||
For compatibility with :func:`typing.get_overloads`, the ``@deprecated``
|
||||
decorator should be placed after the ``@overload`` decorator.
|
||||
|
@ -277,52 +298,14 @@ PEP.
|
|||
Open issues
|
||||
===========
|
||||
|
||||
Runtime warnings
|
||||
----------------
|
||||
``skip_file_prefixes``
|
||||
----------------------
|
||||
|
||||
Users might expect usage of the ``@deprecated`` decorator to issue a
|
||||
:exc:`DeprecationWarning` at runtime. However, this would raise a number of
|
||||
thorny issues:
|
||||
|
||||
* When the decorator is applied to a class or an overload, the warning
|
||||
would not be raised as expected. For classes, the warning could be
|
||||
raised on instantiation, but this would not cover usage in type
|
||||
annotations or :func:`isinstance` checks.
|
||||
* Users may want to control the :func:`~warnings.warn` call in more detail
|
||||
(e.g., changing the warning class).
|
||||
* ``typing.py`` generally aims to avoid affecting runtime behavior.
|
||||
* To raise a warning, the ``@deprecated`` decorator would have to wrap
|
||||
functions and patch ``__new__`` on classes. This would complicate runtime
|
||||
introspection.
|
||||
* Users may not expect usage of an object from the ``typing`` module to
|
||||
affect runtime behavior.
|
||||
|
||||
Users who want to use ``@deprecated`` while also issuing a runtime warning
|
||||
can use the ``if TYPE_CHECKING:`` idiom, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
import functools
|
||||
import warnings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import deprecated
|
||||
else:
|
||||
def deprecated(msg):
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
||||
return func(*args, **kwargs)
|
||||
wrapper.__deprecated__ = msg
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
While this code block looks complex, it could be encapsulated in a library.
|
||||
|
||||
Still, the behavior could be made opt-in, and perhaps the benefits of
|
||||
incorporating a runtime warning outweigh the costs.
|
||||
Python 3.12 `will have support <https://github.com/python/cpython/issues/39615>`__
|
||||
for a ``skip_file_prefixes`` argument to :func:`warnings.warn`, which allows
|
||||
control of where a warning is raised in a more intuitive manner than ``stacklevel``.
|
||||
Should this feature be exposed in ``@deprecated``? If so, how would we provide a
|
||||
backport in ``typing_extensions``?
|
||||
|
||||
Acknowledgments
|
||||
===============
|
||||
|
|
Loading…
Reference in New Issue