]> granicus.if.org Git - python/commitdiff
bpo-37046: PEP 586: Add Literal to typing module (#13572)
authorIvan Levkivskyi <levkivskyi@gmail.com>
Sun, 26 May 2019 08:37:48 +0000 (09:37 +0100)
committerGitHub <noreply@github.com>
Sun, 26 May 2019 08:37:48 +0000 (09:37 +0100)
The implementation is straightforward and essentially is just copied from `typing_extensions`.

Doc/library/typing.rst
Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2019-05-25-19-12-53.bpo-37046.iuhQQj.rst [new file with mode: 0644]

index 8362f1d8e6b793c9aa8b0339beb59c550215916f..e64fecb8544b275a65c03c72e8e1a2d8b9ef0336 100644 (file)
@@ -1103,6 +1103,28 @@ The module defines the following classes, functions and decorators:
    ``Callable[..., Any]``, and in turn to
    :class:`collections.abc.Callable`.
 
+.. data:: Literal
+
+   A type that can be used to indicate to type checkers that the
+   corresponding variable or function parameter has a value equivalent to
+   the provided literal (or one of several literals). For example::
+
+      def validate_simple(data: Any) -> Literal[True]:  # always returns True
+          ...
+
+      MODE = Literal['r', 'rb', 'w', 'wb']
+      def open_helper(file: str, mode: MODE) -> str:
+          ...
+
+      open_helper('/some/path', 'r')  # Passes type check
+      open_helper('/other/path', 'typo')  # Error in type checker
+
+   ``Literal[...]`` cannot be subclassed. At runtime, an arbitrary value
+   is allowed as type argument to ``Literal[...]``, but type checkers may
+   impose restrictions. See :pep:`586` for more details about literal types.
+
+   .. versionadded:: 3.8
+
 .. data:: ClassVar
 
    Special type construct to mark class variables.
index 3d93eb396ce7adce04ae332d3859b59c53993323..eb618936d97b91280b2c62388ad623ced1cec394 100644 (file)
@@ -9,7 +9,7 @@ from copy import copy, deepcopy
 from typing import Any, NoReturn
 from typing import TypeVar, AnyStr
 from typing import T, KT, VT  # Not in __all__.
-from typing import Union, Optional
+from typing import Union, Optional, Literal
 from typing import Tuple, List, MutableMapping
 from typing import Callable
 from typing import Generic, ClassVar, Final, final
@@ -489,6 +489,68 @@ class CallableTests(BaseTestCase):
         typing.List[Callable[..., str]]
 
 
+class LiteralTests(BaseTestCase):
+    def test_basics(self):
+        # All of these are allowed.
+        Literal[1]
+        Literal[1, 2, 3]
+        Literal["x", "y", "z"]
+        Literal[None]
+        Literal[True]
+        Literal[1, "2", False]
+        Literal[Literal[1, 2], Literal[4, 5]]
+        Literal[b"foo", u"bar"]
+
+    def test_illegal_parameters_do_not_raise_runtime_errors(self):
+        # Type checkers should reject these types, but we do not
+        # raise errors at runtime to maintain maximium flexibility.
+        Literal[int]
+        Literal[3j + 2, ..., ()]
+        Literal[{"foo": 3, "bar": 4}]
+        Literal[T]
+
+    def test_literals_inside_other_types(self):
+        List[Literal[1, 2, 3]]
+        List[Literal[("foo", "bar", "baz")]]
+
+    def test_repr(self):
+        self.assertEqual(repr(Literal[1]), "typing.Literal[1]")
+        self.assertEqual(repr(Literal[1, True, "foo"]), "typing.Literal[1, True, 'foo']")
+        self.assertEqual(repr(Literal[int]), "typing.Literal[int]")
+        self.assertEqual(repr(Literal), "typing.Literal")
+        self.assertEqual(repr(Literal[None]), "typing.Literal[None]")
+
+    def test_cannot_init(self):
+        with self.assertRaises(TypeError):
+            Literal()
+        with self.assertRaises(TypeError):
+            Literal[1]()
+        with self.assertRaises(TypeError):
+            type(Literal)()
+        with self.assertRaises(TypeError):
+            type(Literal[1])()
+
+    def test_no_isinstance_or_issubclass(self):
+        with self.assertRaises(TypeError):
+            isinstance(1, Literal[1])
+        with self.assertRaises(TypeError):
+            isinstance(int, Literal[1])
+        with self.assertRaises(TypeError):
+            issubclass(1, Literal[1])
+        with self.assertRaises(TypeError):
+            issubclass(int, Literal[1])
+
+    def test_no_subclassing(self):
+        with self.assertRaises(TypeError):
+            class Foo(Literal[1]): pass
+        with self.assertRaises(TypeError):
+            class Bar(Literal): pass
+
+    def test_no_multiple_subscripts(self):
+        with self.assertRaises(TypeError):
+            Literal[1][1]
+
+
 XK = TypeVar('XK', str, bytes)
 XV = TypeVar('XV')
 
index 06a7eb0dff843750dcbf7cd42cb2a4f6c37fcf3e..1044cc409f410afe49ccbeefc116f86250228361 100644 (file)
@@ -37,6 +37,7 @@ __all__ = [
     'ClassVar',
     'Final',
     'Generic',
+    'Literal',
     'Optional',
     'Tuple',
     'Type',
@@ -355,6 +356,10 @@ class _SpecialForm(_Final, _Immutable, _root=True):
         if self._name == 'Optional':
             arg = _type_check(parameters, "Optional[t] requires a single type.")
             return Union[arg, type(None)]
+        if self._name == 'Literal':
+            # There is no '_type_check' call because arguments to Literal[...] are
+            # values, not types.
+            return _GenericAlias(self, parameters)
         raise TypeError(f"{self} is not subscriptable")
 
 
@@ -451,6 +456,28 @@ Optional = _SpecialForm('Optional', doc=
     Optional[X] is equivalent to Union[X, None].
     """)
 
+Literal = _SpecialForm('Literal', doc=
+    """Special typing form to define literal types (a.k.a. value types).
+
+    This form can be used to indicate to type checkers that the corresponding
+    variable or function parameter has a value equivalent to the provided
+    literal (or one of several literals):
+
+      def validate_simple(data: Any) -> Literal[True]:  # always returns True
+          ...
+
+      MODE = Literal['r', 'rb', 'w', 'wb']
+      def open_helper(file: str, mode: MODE) -> str:
+          ...
+
+      open_helper('/some/path', 'r')  # Passes type check
+      open_helper('/other/path', 'typo')  # Error in type checker
+
+   Literal[...] cannot be subclassed. At runtime, an arbitrary value
+   is allowed as type argument to Literal[...], but type checkers may
+   impose restrictions.
+    """)
+
 
 class ForwardRef(_Final, _root=True):
     """Internal wrapper to hold a forward reference."""
diff --git a/Misc/NEWS.d/next/Library/2019-05-25-19-12-53.bpo-37046.iuhQQj.rst b/Misc/NEWS.d/next/Library/2019-05-25-19-12-53.bpo-37046.iuhQQj.rst
new file mode 100644 (file)
index 0000000..9ec333b
--- /dev/null
@@ -0,0 +1 @@
+PEP 586: Add ``Literal`` to the ``typing`` module.
\ No newline at end of file