Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docs for Literal types #6150

Merged
merged 5 commits into from
Jan 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/source/final_attrs.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _final_attrs:

Final names, methods and classes
================================

Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Mypy is a static type checker for Python 3 and Python 2.7.
stubs
generics
more_types
literal_types
final_attrs
metaclasses

Expand Down
159 changes: 159 additions & 0 deletions docs/source/literal_types.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
.. _literal_types:

Literal types
=============

.. note::

Literal is an officially supported feature, but is highly experimental
and should be considered to be in alpha stage. It is very likely that future
releases of mypy will modify the behavior of literal types, either by adding
new features or by tuning or removing problematic ones.

Literal types let you indicate that an expression is equal to some specific
primitive value. For example, if we annotate a variable with type ``Literal["foo"]``,
mypy will understand that variable is not only of type ``str``, but is also
equal to specifically the string ``"foo"``.

This feature is primarily useful when annotating functions that behave
differently based on the exact value the caller provides. For example,
suppose we have a function ``fetch_data(...)`` that returns ``bytes`` if the
first argument is ``True``, and ``str`` if it's ``False``. We can construct a
precise type signature for this function using ``Literal[...]`` and overloads:

.. code-block:: python

from typing import overload, Union
from typing_extensions import Literal

# The first two overloads use Literal[...] so we can
# have precise return types:

@overload
def fetch_data(raw: Literal[True]) -> bytes: ...
@overload
def fetch_data(raw: Literal[False]) -> str: ...

# The last overload is a fallback in case the caller
# provides a regular bool:

@overload
def fetch_data(raw: bool) -> Union[bytes, str]: ...

def fetch_data(raw: bool) -> Union[bytes, str]:
# Implementation is omitted
...

reveal_type(fetch_data(True)) # Revealed type is 'bytes'
reveal_type(fetch_data(False)) # Revealed type is 'str'

# Variables declared without annotations will continue to have an
# inferred type of 'bool'.

variable = True
reveal_type(fetch_data(variable)) # Revealed type is 'Union[bytes, str]'

Parameterizing Literals
***********************

Literal types may contain one or more literal bools, ints, strs, and bytes.
However, literal types **cannot** contain arbitrary expressions:
types like ``Literal[my_string.trim()]``, ``Literal[x > 3]``, or ``Literal[3j + 4]``
are all illegal.

Literals containing two or more values are equivalent to the union of those values.
So, ``Literal[-3, b"foo", True]`` is equivalent to
``Union[Literal[-3], Literal[b"foo"], Literal[True]]``. This makes writing
more complex types involving literals a little more convenient.

Literal types may also contain ``None``. Mypy will treat ``Literal[None]`` as being
equivalent to just ``None``. This means that ``Literal[4, None]``,
``Union[Literal[4], None]``, and ``Optional[Literal[4]]`` are all equivalent.

Literals may also contain aliases to other literal types. For example, the
following program is legal:

.. code-block:: python

PrimaryColors = Literal["red", "blue", "yellow"]
SecondaryColors = Literal["purple", "green", "orange"]
AllowedColors = Literal[PrimaryColors, SecondaryColors]

def paint(color: AllowedColors) -> None: ...

paint("red") # Type checks!
paint("turquoise") # Does not type check

Literals may not contain any other kind of type or expression. This means doing
``Literal[my_instance]``, ``Literal[Any]``, ``Literal[3.14]``, or
``Literal[{"foo": 2, "bar": 5}]`` are all illegal.

Future versions of mypy may relax some of these restrictions. For example, we
plan on adding support for using enum values inside ``Literal[...]`` in an upcoming release.

Declaring literal variables
***************************

You must explicitly add an annotation to a variable to declare that it has
a literal type:

.. code-block:: python

a: Literal[19] = 19
reveal_type(a) # Revealed type is 'Literal[19]'

In order to preserve backwards-compatibility, variables without this annotation
are **not** assumed to be literals:

.. code-block:: python

b = 19
reveal_type(b) # Revealed type is 'int'

If you find repeating the value of the variable in the type hint to be tedious,
you can instead change the variable to be :ref:`Final <final_attrs>`:

.. code-block:: python

from typing_extensions import Final, Literal

def expects_literal(x: Literal[19]) -> None: pass

c: Final = 19

reveal_type(c) # Revealed type is 'int'
expects_literal(c) # ...but this type checks!

If you do not provide an explicit type in the Final, the type of ``c`` becomes
context-sensitive: mypy will basically try "substituting" the original assigned
value whenever it's used before performing type checking. So, mypy will type-check
the above program almost as if it were written like so:

.. code-block:: python

from typing_extensions import Final, Literal

def expects_literal(x: Literal[19]) -> None: pass

reveal_type(19)
expects_literal(19)

This is why ``expects_literal(19)`` type-checks despite the fact that ``reveal_type(c)``
reports ``int``.

So while changing a variable to be Final is not quite the same thing as adding
an explicit ``Literal[...]`` annotation, it often leads to the same effect in practice.

Limitations
***********

Mypy will not understand expressions that use variables of type ``Literal[..]``
on a deep level. For example, if you have a variable ``a`` of type ``Literal[3]``
and another variable ``b`` of type ``Literal[5]``, mypy will infer that
``a + b`` has type ``int``, **not** type ``Literal[8]``.

The basic rule is that literal types are treated as just regular subtypes of
whatever type the parameter has. For example, ``Literal[3]`` is treated as a
subtype of ``int`` and so will inherit all of ``int``'s methods directly. This
means that ``Literal[3].__add__`` accepts the same arguments and has the same
return type as ``int.__add__``.
4 changes: 2 additions & 2 deletions docs/source/more_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ More types
==========

This section introduces a few additional kinds of types, including ``NoReturn``,
``NewType``, ``TypedDict``, and types for async code. It also discusses how to
give functions more precise types using overloads. All of these are only
``NewType``, ``TypedDict``, and types for async code. It also discusses
how to give functions more precise types using overloads. All of these are only
situationally useful, so feel free to skip this section and come back when you
have a need for some of them.

Expand Down