FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

PEP 702: Runtime warnings by default by JelleZijlstra · Pull Request #3006 · python/peps · GitHub

Repository navigation

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .rst  (1) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
83 changes: 33 additions & 50 deletions pep-0702.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -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
==========
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -277,52 +298,14 @@ PEP.
Open issues
===========

Runtime warnings
----------------

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
``skip_file_prefixes``
----------------------

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
===============
Expand Down

Back | FazBrowse Home | New Git URL