[Python-checkins] gh-101561: Add typing.override decorator (#101564)
JelleZijlstra
webhook-mailer at python.org
Mon Feb 27 16:16:38 EST 2023
https://github.com/python/cpython/commit/0f89acf6cc4d4790f7b7a82165d0a6e7e84e4b72
commit: 0f89acf6cc4d4790f7b7a82165d0a6e7e84e4b72
branch: main
author: Steven Troxler <steven.troxler at gmail.com>
committer: JelleZijlstra <jelle.zijlstra at gmail.com>
date: 2023-02-27T13:16:11-08:00
summary:
gh-101561: Add typing.override decorator (#101564)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra at gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood at Gmail.com>
files:
A Misc/NEWS.d/next/Library/2023-02-04-16-35-46.gh-issue-101561.Xo6pIZ.rst
M Doc/library/typing.rst
M Doc/whatsnew/3.12.rst
M Lib/test/test_typing.py
M Lib/typing.py
M Misc/ACKS
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index bbbf6920ddec..3395e4bfb95c 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -91,6 +91,8 @@ annotations. These include:
*Introducing* :data:`LiteralString`
* :pep:`681`: Data Class Transforms
*Introducing* the :func:`@dataclass_transform<dataclass_transform>` decorator
+* :pep:`698`: Adding an override decorator to typing
+ *Introducing* the :func:`@override<override>` decorator
.. _type-aliases:
@@ -2722,6 +2724,42 @@ Functions and decorators
This wraps the decorator with something that wraps the decorated
function in :func:`no_type_check`.
+
+.. decorator:: override
+
+ A decorator for methods that indicates to type checkers that this method
+ should override a method or attribute with the same name on a base class.
+ This helps prevent bugs that may occur when a base class is changed without
+ an equivalent change to a child class.
+
+ For example::
+
+ class Base:
+ def log_status(self)
+
+ class Sub(Base):
+ @override
+ def log_status(self) -> None: # Okay: overrides Base.log_status
+ ...
+
+ @override
+ def done(self) -> None: # Error reported by type checker
+ ...
+
+ There is no runtime checking of this property.
+
+ The decorator will set the ``__override__`` attribute to ``True`` on
+ the decorated object. Thus, a check like
+ ``if getattr(obj, "__override__", False)`` can be used at runtime to determine
+ whether an object ``obj`` has been marked as an override. If the decorated object
+ does not support setting attributes, the decorator returns the object unchanged
+ without raising an exception.
+
+ See :pep:`698` for more details.
+
+ .. versionadded:: 3.12
+
+
.. decorator:: type_check_only
Decorator to mark a class or function to be unavailable at runtime.
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index e551c5b4fd06..1a25ec6b7061 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -350,6 +350,14 @@ tempfile
The :class:`tempfile.NamedTemporaryFile` function has a new optional parameter
*delete_on_close* (Contributed by Evgeny Zorin in :gh:`58451`.)
+typing
+------
+
+* Add :func:`typing.override`, an override decorator telling to static type
+ checkers to verify that a method overrides some method or attribute of the
+ same name on a base class, as per :pep:`698`. (Contributed by Steven Troxler in
+ :gh:`101564`.)
+
sys
---
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 7a460d94469f..d61dc6e2fbd7 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -23,6 +23,7 @@
from typing import assert_type, cast, runtime_checkable
from typing import get_type_hints
from typing import get_origin, get_args
+from typing import override
from typing import is_typeddict
from typing import reveal_type
from typing import dataclass_transform
@@ -4166,6 +4167,43 @@ def cached(self): ...
self.assertIs(True, Methods.cached.__final__)
+class OverrideDecoratorTests(BaseTestCase):
+ def test_override(self):
+ class Base:
+ def normal_method(self): ...
+ @staticmethod
+ def static_method_good_order(): ...
+ @staticmethod
+ def static_method_bad_order(): ...
+ @staticmethod
+ def decorator_with_slots(): ...
+
+ class Derived(Base):
+ @override
+ def normal_method(self):
+ return 42
+
+ @staticmethod
+ @override
+ def static_method_good_order():
+ return 42
+
+ @override
+ @staticmethod
+ def static_method_bad_order():
+ return 42
+
+
+ self.assertIsSubclass(Derived, Base)
+ instance = Derived()
+ self.assertEqual(instance.normal_method(), 42)
+ self.assertIs(True, instance.normal_method.__override__)
+ self.assertEqual(Derived.static_method_good_order(), 42)
+ self.assertIs(True, Derived.static_method_good_order.__override__)
+ self.assertEqual(Derived.static_method_bad_order(), 42)
+ self.assertIs(False, hasattr(Derived.static_method_bad_order, "__override__"))
+
+
class CastTests(BaseTestCase):
def test_basics(self):
diff --git a/Lib/typing.py b/Lib/typing.py
index bdf51bb5f415..8d40e923bb1d 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -138,6 +138,7 @@ def _idfunc(_, x):
'NoReturn',
'NotRequired',
'overload',
+ 'override',
'ParamSpecArgs',
'ParamSpecKwargs',
'Required',
@@ -2657,6 +2658,7 @@ class Other(Leaf): # Error reported by type checker
# Internal type variable used for Type[].
CT_co = TypeVar('CT_co', covariant=True, bound=type)
+
# A useful type variable with constraints. This represents string types.
# (This one *is* for export!)
AnyStr = TypeVar('AnyStr', bytes, str)
@@ -2748,6 +2750,8 @@ def new_user(user_class: Type[U]) -> U:
At this point the type checker knows that joe has type BasicUser.
"""
+# Internal type variable for callables. Not for export.
+F = TypeVar("F", bound=Callable[..., Any])
@runtime_checkable
class SupportsInt(Protocol):
@@ -3448,3 +3452,40 @@ def decorator(cls_or_fn):
}
return cls_or_fn
return decorator
+
+
+
+def override(method: F, /) -> F:
+ """Indicate that a method is intended to override a method in a base class.
+
+ Usage:
+
+ class Base:
+ def method(self) -> None: ...
+ pass
+
+ class Child(Base):
+ @override
+ def method(self) -> None:
+ super().method()
+
+ When this decorator is applied to a method, the type checker will
+ validate that it overrides a method or attribute with the same name on a
+ base class. This helps prevent bugs that may occur when a base class is
+ changed without an equivalent change to a child class.
+
+ There is no runtime checking of this property. The decorator sets the
+ ``__override__`` attribute to ``True`` on the decorated object to allow
+ runtime introspection.
+
+ See PEP 698 for details.
+
+ """
+ try:
+ method.__override__ = True
+ except (AttributeError, TypeError):
+ # Skip the attribute silently if it is not writable.
+ # AttributeError happens if the object has __slots__ or a
+ # read-only property, TypeError if it's a builtin class.
+ pass
+ return method
diff --git a/Misc/ACKS b/Misc/ACKS
index 3403aee4cc78..2da3d0ab29b8 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1848,6 +1848,7 @@ Tom Tromey
John Tromp
Diane Trout
Jason Trowbridge
+Steven Troxler
Brent Tubbs
Anthony Tuininga
Erno Tukia
diff --git a/Misc/NEWS.d/next/Library/2023-02-04-16-35-46.gh-issue-101561.Xo6pIZ.rst b/Misc/NEWS.d/next/Library/2023-02-04-16-35-46.gh-issue-101561.Xo6pIZ.rst
new file mode 100644
index 000000000000..2f6a4153062e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-02-04-16-35-46.gh-issue-101561.Xo6pIZ.rst
@@ -0,0 +1 @@
+Add a new decorator :func:`typing.override`. See :pep:`698` for details. Patch by Steven Troxler.
More information about the Python-checkins
mailing list