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
|
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
|
Motivation
|
||||||
==========
|
==========
|
||||||
|
@ -175,10 +176,30 @@ Here is how type checkers should handle usage of this library:
|
||||||
Runtime behavior
|
Runtime behavior
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
At runtime, the decorator sets an attribute ``__deprecated__`` on the decorated
|
The ``@deprecated`` parameter takes two keyword-only arguments:
|
||||||
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
|
* ``category``: A warning class. Defaults to :exc:`DeprecationWarning`. If this
|
||||||
:exc:`DeprecationWarning`.
|
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``
|
For compatibility with :func:`typing.get_overloads`, the ``@deprecated``
|
||||||
decorator should be placed after the ``@overload`` decorator.
|
decorator should be placed after the ``@overload`` decorator.
|
||||||
|
@ -277,52 +298,14 @@ PEP.
|
||||||
Open issues
|
Open issues
|
||||||
===========
|
===========
|
||||||
|
|
||||||
Runtime warnings
|
``skip_file_prefixes``
|
||||||
----------------
|
----------------------
|
||||||
|
|
||||||
Users might expect usage of the ``@deprecated`` decorator to issue a
|
Python 3.12 `will have support <https://github.com/python/cpython/issues/39615>`__
|
||||||
:exc:`DeprecationWarning` at runtime. However, this would raise a number of
|
for a ``skip_file_prefixes`` argument to :func:`warnings.warn`, which allows
|
||||||
thorny issues:
|
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
|
||||||
* When the decorator is applied to a class or an overload, the warning
|
backport in ``typing_extensions``?
|
||||||
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.
|
|
||||||
|
|
||||||
Acknowledgments
|
Acknowledgments
|
||||||
===============
|
===============
|
||||||
|
|
Loading…
Reference in New Issue