summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Troxler <steven.troxler@gmail.com>2023-02-27 21:16:11 (GMT)
committerGitHub <noreply@github.com>2023-02-27 21:16:11 (GMT)
commit0f89acf6cc4d4790f7b7a82165d0a6e7e84e4b72 (patch)
treeb411d170426a326dc109e23e3cf0d42a6d770337
parent4624987b296108c2dc1e6e3a24e65d2de7afd451 (diff)
downloadcpython-0f89acf6cc4d4790f7b7a82165d0a6e7e84e4b72.zip
cpython-0f89acf6cc4d4790f7b7a82165d0a6e7e84e4b72.tar.gz
cpython-0f89acf6cc4d4790f7b7a82165d0a6e7e84e4b72.tar.bz2
gh-101561: Add typing.override decorator (#101564)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
-rw-r--r--Doc/library/typing.rst38
-rw-r--r--Doc/whatsnew/3.12.rst8
-rw-r--r--Lib/test/test_typing.py38
-rw-r--r--Lib/typing.py41
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/Library/2023-02-04-16-35-46.gh-issue-101561.Xo6pIZ.rst1
6 files changed, 127 insertions, 0 deletions
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index bbbf692..3395e4b 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 e551c5b..1a25ec6 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 7a460d9..d61dc6e 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -23,6 +23,7 @@ from typing import Generic, ClassVar, Final, final, Protocol
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 @@ class FinalDecoratorTests(BaseTestCase):
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 bdf51bb..8d40e92 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -138,6 +138,7 @@ __all__ = [
'NoReturn',
'NotRequired',
'overload',
+ 'override',
'ParamSpecArgs',
'ParamSpecKwargs',
'Required',
@@ -2657,6 +2658,7 @@ T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant.
# 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 @@ Type.__doc__ = \
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 dataclass_transform(
}
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 3403aee..2da3d0a 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 0000000..2f6a415
--- /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.