Skip to content

Commit 0f89acf

Browse files
stroxlerJelleZijlstraAlexWaygood
authored
gh-101561: Add typing.override decorator (#101564)
Co-authored-by: Jelle Zijlstra <[email protected]> Co-authored-by: Alex Waygood <[email protected]>
1 parent 4624987 commit 0f89acf

File tree

6 files changed

+127
-0
lines changed

6 files changed

+127
-0
lines changed

Doc/library/typing.rst

+38
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ annotations. These include:
9191
*Introducing* :data:`LiteralString`
9292
* :pep:`681`: Data Class Transforms
9393
*Introducing* the :func:`@dataclass_transform<dataclass_transform>` decorator
94+
* :pep:`698`: Adding an override decorator to typing
95+
*Introducing* the :func:`@override<override>` decorator
9496

9597
.. _type-aliases:
9698

@@ -2722,6 +2724,42 @@ Functions and decorators
27222724
This wraps the decorator with something that wraps the decorated
27232725
function in :func:`no_type_check`.
27242726

2727+
2728+
.. decorator:: override
2729+
2730+
A decorator for methods that indicates to type checkers that this method
2731+
should override a method or attribute with the same name on a base class.
2732+
This helps prevent bugs that may occur when a base class is changed without
2733+
an equivalent change to a child class.
2734+
2735+
For example::
2736+
2737+
class Base:
2738+
def log_status(self)
2739+
2740+
class Sub(Base):
2741+
@override
2742+
def log_status(self) -> None: # Okay: overrides Base.log_status
2743+
...
2744+
2745+
@override
2746+
def done(self) -> None: # Error reported by type checker
2747+
...
2748+
2749+
There is no runtime checking of this property.
2750+
2751+
The decorator will set the ``__override__`` attribute to ``True`` on
2752+
the decorated object. Thus, a check like
2753+
``if getattr(obj, "__override__", False)`` can be used at runtime to determine
2754+
whether an object ``obj`` has been marked as an override. If the decorated object
2755+
does not support setting attributes, the decorator returns the object unchanged
2756+
without raising an exception.
2757+
2758+
See :pep:`698` for more details.
2759+
2760+
.. versionadded:: 3.12
2761+
2762+
27252763
.. decorator:: type_check_only
27262764

27272765
Decorator to mark a class or function to be unavailable at runtime.

Doc/whatsnew/3.12.rst

+8
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,14 @@ tempfile
350350
The :class:`tempfile.NamedTemporaryFile` function has a new optional parameter
351351
*delete_on_close* (Contributed by Evgeny Zorin in :gh:`58451`.)
352352

353+
typing
354+
------
355+
356+
* Add :func:`typing.override`, an override decorator telling to static type
357+
checkers to verify that a method overrides some method or attribute of the
358+
same name on a base class, as per :pep:`698`. (Contributed by Steven Troxler in
359+
:gh:`101564`.)
360+
353361
sys
354362
---
355363

Lib/test/test_typing.py

+38
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from typing import assert_type, cast, runtime_checkable
2424
from typing import get_type_hints
2525
from typing import get_origin, get_args
26+
from typing import override
2627
from typing import is_typeddict
2728
from typing import reveal_type
2829
from typing import dataclass_transform
@@ -4166,6 +4167,43 @@ def cached(self): ...
41664167
self.assertIs(True, Methods.cached.__final__)
41674168

41684169

4170+
class OverrideDecoratorTests(BaseTestCase):
4171+
def test_override(self):
4172+
class Base:
4173+
def normal_method(self): ...
4174+
@staticmethod
4175+
def static_method_good_order(): ...
4176+
@staticmethod
4177+
def static_method_bad_order(): ...
4178+
@staticmethod
4179+
def decorator_with_slots(): ...
4180+
4181+
class Derived(Base):
4182+
@override
4183+
def normal_method(self):
4184+
return 42
4185+
4186+
@staticmethod
4187+
@override
4188+
def static_method_good_order():
4189+
return 42
4190+
4191+
@override
4192+
@staticmethod
4193+
def static_method_bad_order():
4194+
return 42
4195+
4196+
4197+
self.assertIsSubclass(Derived, Base)
4198+
instance = Derived()
4199+
self.assertEqual(instance.normal_method(), 42)
4200+
self.assertIs(True, instance.normal_method.__override__)
4201+
self.assertEqual(Derived.static_method_good_order(), 42)
4202+
self.assertIs(True, Derived.static_method_good_order.__override__)
4203+
self.assertEqual(Derived.static_method_bad_order(), 42)
4204+
self.assertIs(False, hasattr(Derived.static_method_bad_order, "__override__"))
4205+
4206+
41694207
class CastTests(BaseTestCase):
41704208

41714209
def test_basics(self):

Lib/typing.py

+41
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def _idfunc(_, x):
138138
'NoReturn',
139139
'NotRequired',
140140
'overload',
141+
'override',
141142
'ParamSpecArgs',
142143
'ParamSpecKwargs',
143144
'Required',
@@ -2657,6 +2658,7 @@ class Other(Leaf): # Error reported by type checker
26572658
# Internal type variable used for Type[].
26582659
CT_co = TypeVar('CT_co', covariant=True, bound=type)
26592660

2661+
26602662
# A useful type variable with constraints. This represents string types.
26612663
# (This one *is* for export!)
26622664
AnyStr = TypeVar('AnyStr', bytes, str)
@@ -2748,6 +2750,8 @@ def new_user(user_class: Type[U]) -> U:
27482750
At this point the type checker knows that joe has type BasicUser.
27492751
"""
27502752

2753+
# Internal type variable for callables. Not for export.
2754+
F = TypeVar("F", bound=Callable[..., Any])
27512755

27522756
@runtime_checkable
27532757
class SupportsInt(Protocol):
@@ -3448,3 +3452,40 @@ def decorator(cls_or_fn):
34483452
}
34493453
return cls_or_fn
34503454
return decorator
3455+
3456+
3457+
3458+
def override(method: F, /) -> F:
3459+
"""Indicate that a method is intended to override a method in a base class.
3460+
3461+
Usage:
3462+
3463+
class Base:
3464+
def method(self) -> None: ...
3465+
pass
3466+
3467+
class Child(Base):
3468+
@override
3469+
def method(self) -> None:
3470+
super().method()
3471+
3472+
When this decorator is applied to a method, the type checker will
3473+
validate that it overrides a method or attribute with the same name on a
3474+
base class. This helps prevent bugs that may occur when a base class is
3475+
changed without an equivalent change to a child class.
3476+
3477+
There is no runtime checking of this property. The decorator sets the
3478+
``__override__`` attribute to ``True`` on the decorated object to allow
3479+
runtime introspection.
3480+
3481+
See PEP 698 for details.
3482+
3483+
"""
3484+
try:
3485+
method.__override__ = True
3486+
except (AttributeError, TypeError):
3487+
# Skip the attribute silently if it is not writable.
3488+
# AttributeError happens if the object has __slots__ or a
3489+
# read-only property, TypeError if it's a builtin class.
3490+
pass
3491+
return method

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -1848,6 +1848,7 @@ Tom Tromey
18481848
John Tromp
18491849
Diane Trout
18501850
Jason Trowbridge
1851+
Steven Troxler
18511852
Brent Tubbs
18521853
Anthony Tuininga
18531854
Erno Tukia
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a new decorator :func:`typing.override`. See :pep:`698` for details. Patch by Steven Troxler.

0 commit comments

Comments
 (0)