From f367242d10ef36db38133a39ab7627f63099cba4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 26 May 2019 09:37:07 +0100 Subject: [PATCH] bpo-37045: PEP 591: Add final qualifiers to typing module (GH-13571) The implementation is straightforward, it just mimics `ClassVar` (since the latter is also a name/access qualifier, not really a type). Also it is essentially copied from `typing_extensions`. --- Doc/library/typing.rst | 44 ++++++++++++++++ Lib/test/test_typing.py | 49 ++++++++++++++++- Lib/typing.py | 52 +++++++++++++++++-- .../2019-05-25-18-36-50.bpo-37045.suHdVJ.rst | 1 + 4 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-05-25-18-36-50.bpo-37045.suHdVJ.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 86a3db8467e..8362f1d8e6b 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -940,6 +940,31 @@ The module defines the following classes, functions and decorators: See :pep:`484` for details and comparison with other typing semantics. +.. decorator:: final + + A decorator to indicate to type checkers that the decorated method + cannot be overridden, and the decorated class cannot be subclassed. + For example:: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. See :pep:`591` for + more details. + + .. versionadded:: 3.8 + .. decorator:: no_type_check Decorator to indicate that annotations are not type hints. @@ -1104,6 +1129,25 @@ The module defines the following classes, functions and decorators: .. versionadded:: 3.5.3 +.. data:: Final + + A special typing construct to indicate to type checkers that a name + cannot be re-assigned or overridden in a subclass. For example:: + + MAX_SIZE: Final = 9000 + MAX_SIZE += 1 # Error reported by type checker + + class Connection: + TIMEOUT: Final[int] = 10 + + class FastConnector(Connection): + TIMEOUT = 1 # Error reported by type checker + + There is no runtime checking of these properties. See :pep:`591` for + more details. + + .. versionadded:: 3.8 + .. data:: AnyStr ``AnyStr`` is a type variable defined as diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index c9bfd0c7ed7..3d93eb396ce 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -12,7 +12,7 @@ from typing import T, KT, VT # Not in __all__. from typing import Union, Optional from typing import Tuple, List, MutableMapping from typing import Callable -from typing import Generic, ClassVar +from typing import Generic, ClassVar, Final, final from typing import cast from typing import get_type_hints from typing import no_type_check, no_type_check_decorator @@ -1438,6 +1438,53 @@ class ClassVarTests(BaseTestCase): issubclass(int, ClassVar) +class FinalTests(BaseTestCase): + + def test_basics(self): + Final[int] # OK + with self.assertRaises(TypeError): + Final[1] + with self.assertRaises(TypeError): + Final[int, str] + with self.assertRaises(TypeError): + Final[int][str] + with self.assertRaises(TypeError): + Optional[Final[int]] + + def test_repr(self): + self.assertEqual(repr(Final), 'typing.Final') + cv = Final[int] + self.assertEqual(repr(cv), 'typing.Final[int]') + cv = Final[Employee] + self.assertEqual(repr(cv), 'typing.Final[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Final)): + pass + with self.assertRaises(TypeError): + class C(type(Final[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Final() + with self.assertRaises(TypeError): + type(Final)() + with self.assertRaises(TypeError): + type(Final[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Final[int]) + with self.assertRaises(TypeError): + issubclass(int, Final) + + def test_final_unmodified(self): + def func(x): ... + self.assertIs(func, final(func)) + + class CastTests(BaseTestCase): def test_basics(self): diff --git a/Lib/typing.py b/Lib/typing.py index 7aab1628a31..06a7eb0dff8 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -35,6 +35,7 @@ __all__ = [ 'Any', 'Callable', 'ClassVar', + 'Final', 'Generic', 'Optional', 'Tuple', @@ -92,6 +93,7 @@ __all__ = [ # One-off things. 'AnyStr', 'cast', + 'final', 'get_type_hints', 'NewType', 'no_type_check', @@ -121,7 +123,7 @@ def _type_check(arg, msg, is_argument=True): """ invalid_generic_forms = (Generic, _Protocol) if is_argument: - invalid_generic_forms = invalid_generic_forms + (ClassVar, ) + invalid_generic_forms = invalid_generic_forms + (ClassVar, Final) if arg is None: return type(None) @@ -336,8 +338,8 @@ class _SpecialForm(_Final, _Immutable, _root=True): @_tp_cache def __getitem__(self, parameters): - if self._name == 'ClassVar': - item = _type_check(parameters, 'ClassVar accepts only single type.') + if self._name in ('ClassVar', 'Final'): + item = _type_check(parameters, f'{self._name} accepts only single type.') return _GenericAlias(self, (item,)) if self._name == 'Union': if parameters == (): @@ -398,6 +400,24 @@ ClassVar = _SpecialForm('ClassVar', doc= be used with isinstance() or issubclass(). """) +Final = _SpecialForm('Final', doc= + """Special typing construct to indicate final names to type checkers. + + A final name cannot be re-assigned or overridden in a subclass. + For example: + + MAX_SIZE: Final = 9000 + MAX_SIZE += 1 # Error reported by type checker + + class Connection: + TIMEOUT: Final[int] = 10 + + class FastConnector(Connection): + TIMEOUT = 1 # Error reported by type checker + + There is no runtime checking of these properties. + """) + Union = _SpecialForm('Union', doc= """Union type; Union[X, Y] means either X or Y. @@ -1085,6 +1105,32 @@ def overload(func): return _overload_dummy +def final(f): + """A decorator to indicate final methods and final classes. + + Use this decorator to indicate to type checkers that the decorated + method cannot be overridden, and decorated class cannot be subclassed. + For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. + """ + return f + + class _ProtocolMeta(type): """Internal metaclass for _Protocol. diff --git a/Misc/NEWS.d/next/Library/2019-05-25-18-36-50.bpo-37045.suHdVJ.rst b/Misc/NEWS.d/next/Library/2019-05-25-18-36-50.bpo-37045.suHdVJ.rst new file mode 100644 index 00000000000..001529ed6db --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-05-25-18-36-50.bpo-37045.suHdVJ.rst @@ -0,0 +1 @@ +PEP 591: Add ``Final`` qualifier and ``@final`` decorator to the ``typing`` module. \ No newline at end of file