Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-101561: Add typing.override decorator #101564

Merged
merged 10 commits into from
Feb 27, 2023
38 changes: 38 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---

Expand Down
38 changes: 38 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
41 changes: 41 additions & 0 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def _idfunc(_, x):
'NoReturn',
'NotRequired',
'overload',
'override',
'ParamSpecArgs',
'ParamSpecKwargs',
'Required',
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1848,6 +1848,7 @@ Tom Tromey
John Tromp
Diane Trout
Jason Trowbridge
Steven Troxler
Brent Tubbs
Anthony Tuininga
Erno Tukia
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a new decorator :func:`typing.override`. See :pep:`698` for details. Patch by Steven Troxler.