Skip to content

Commit

Permalink
bpo-37046: PEP 586: Add Literal to typing module (#13572)
Browse files Browse the repository at this point in the history
The implementation is straightforward and essentially is just copied from `typing_extensions`.
  • Loading branch information
ilevkivskyi authored May 26, 2019
1 parent f367242 commit b891c46
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 1 deletion.
22 changes: 22 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
64 changes: 63 additions & 1 deletion Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -489,6 +489,68 @@ def test_ellipsis_in_generic(self):
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')

Expand Down
27 changes: 27 additions & 0 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
'ClassVar',
'Final',
'Generic',
'Literal',
'Optional',
'Tuple',
'Type',
Expand Down Expand Up @@ -355,6 +356,10 @@ def __getitem__(self, parameters):
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")


Expand Down Expand Up @@ -451,6 +456,28 @@ class FastConnector(Connection):
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."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PEP 586: Add ``Literal`` to the ``typing`` module.

0 comments on commit b891c46

Please sign in to comment.