bpo-37046: PEP 586: Add Literal to typing module (#13572)
The implementation is straightforward and essentially is just copied from `typing_extensions`.
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 8362f1d..e64fecb 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -1103,6 +1103,28 @@
``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.
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 3d93eb3..eb61893 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -9,7 +9,7 @@
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 @@
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')
diff --git a/Lib/typing.py b/Lib/typing.py
index 06a7eb0..1044cc4 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -37,6 +37,7 @@
'ClassVar',
'Final',
'Generic',
+ 'Literal',
'Optional',
'Tuple',
'Type',
@@ -355,6 +356,10 @@
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[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
index 0000000..9ec333b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-25-19-12-53.bpo-37046.iuhQQj.rst
@@ -0,0 +1 @@
+PEP 586: Add ``Literal`` to the ``typing`` module.
\ No newline at end of file