Skip to content
Next Next commit
PEP 698: Add typing.override decorator
Testing:

First set up the repo, following instructions
at https://devguide.python.org/ by running:
```
./configure --with-pydebug && make -j
```

Then run the typing tests:
```
./python -m test test_typing -v
```

I ran the full test suite with
```
./python -m test -j3
```
and it came back clean except for a `test_grp` failure which I seem to
get on trunk as well - likely something in my build is misconfigured
but I'm pretty sure it is unrelated to the changes here.
  • Loading branch information
stroxler committed Feb 27, 2023
commit e2349bb8fffbd7d6b9043c026ed92dcc0b06a762
36 changes: 36 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,40 @@ 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.

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, "__final__", False)`` can be used at runtime to determine
whether an object ``obj`` has been marked as final. If the decorated object
Comment thread
stroxler marked this conversation as resolved.
Outdated
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
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(__arg: F) -> F:
Comment thread
stroxler marked this conversation as resolved.
Outdated
"""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:
__arg.__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 __arg