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>
This commit is contained in:
Steven Troxler 2023-02-27 13:16:11 -08:00 committed by GitHub
parent 4624987b29
commit 0f89acf6cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 127 additions and 0 deletions

View file

@ -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.

View file

@ -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
---

View file

@ -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):

View file

@ -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

View file

@ -1848,6 +1848,7 @@ Tom Tromey
John Tromp
Diane Trout
Jason Trowbridge
Steven Troxler
Brent Tubbs
Anthony Tuininga
Erno Tukia

View file

@ -0,0 +1 @@
Add a new decorator :func:`typing.override`. See :pep:`698` for details. Patch by Steven Troxler.