]> granicus.if.org Git - python/commitdiff
bpo-37045: PEP 591: Add final qualifiers to typing module (GH-13571)
authorIvan Levkivskyi <levkivskyi@gmail.com>
Sun, 26 May 2019 08:37:07 +0000 (09:37 +0100)
committerGitHub <noreply@github.com>
Sun, 26 May 2019 08:37:07 +0000 (09:37 +0100)
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
Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2019-05-25-18-36-50.bpo-37045.suHdVJ.rst [new file with mode: 0644]

index 86a3db8467ec742481d5c46a555a44594e8cf4a5..8362f1d8e6b793c9aa8b0339beb59c550215916f 100644 (file)
@@ -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
index c9bfd0c7ed720deba8fef2d761ce58f7c263f8f6..3d93eb396ce7adce04ae332d3859b59c53993323 100644 (file)
@@ -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):
index 7aab1628a3192b959c7954a84b0a89a57984f333..06a7eb0dff843750dcbf7cd42cb2a4f6c37fcf3e 100644 (file)
@@ -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 (file)
index 0000000..001529e
--- /dev/null
@@ -0,0 +1 @@
+PEP 591: Add ``Final`` qualifier and ``@final`` decorator to the ``typing`` module.
\ No newline at end of file