[Python-checkins] bpo-37045: PEP 591: Add final qualifiers to typing module (GH-13571)

Ivan Levkivskyi webhook-mailer at python.org
Sun May 26 04:37:21 EDT 2019


https://github.com/python/cpython/commit/f367242d10ef36db38133a39ab7627f63099cba4
commit: f367242d10ef36db38133a39ab7627f63099cba4
branch: master
author: Ivan Levkivskyi <levkivskyi at gmail.com>
committer: GitHub <noreply at github.com>
date: 2019-05-26T09:37:07+01:00
summary:

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

files:
A Misc/NEWS.d/next/Library/2019-05-25-18-36-50.bpo-37045.suHdVJ.rst
M Doc/library/typing.rst
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 86a3db8467ec..8362f1d8e6b7 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 c9bfd0c7ed72..3d93eb396ce7 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -12,7 +12,7 @@
 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 @@ def test_no_isinstance(self):
             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 7aab1628a319..06a7eb0dff84 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -35,6 +35,7 @@
     'Any',
     'Callable',
     'ClassVar',
+    'Final',
     'Generic',
     'Optional',
     'Tuple',
@@ -92,6 +93,7 @@
     # 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 @@ def __subclasscheck__(self, cls):
 
     @_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 @@ class Starship:
     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 utf8(value):
     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 000000000000..001529ed6db4
--- /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



More information about the Python-checkins mailing list