-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Add docs for Literal types #6150
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
Changes from 1 commit
9a37980
0adfa64
dc978fc
6e95ca1
5b6041b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
.. _final_attrs: | ||
|
||
Final names, methods and classes | ||
================================ | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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``, ``Literal``, 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. | ||
|
||
|
@@ -23,6 +23,10 @@ Here's a quick summary of what's covered here: | |
* ``TypedDict`` lets you give precise types for dictionaries that represent | ||
objects with a fixed schema, such as ``{'id': 1, 'items': ['x']}``. | ||
|
||
* ``Literal`` lets you indicate that a given expression has a specific value. | ||
For example, if you do ``x: Literal["foo"]``, mypy will understand that | ||
``x`` is not only a string, but is equal to exactly the string ``"foo"``. | ||
|
||
* Async types let you type check programs using ``async`` and ``await``. | ||
|
||
.. _noreturn: | ||
|
@@ -985,3 +989,165 @@ and non-required keys, such as ``Movie`` above, will only be compatible with | |
another TypedDict if all required keys in the other TypedDict are required keys in the | ||
first TypedDict, and all non-required keys of the other TypedDict are also non-required keys | ||
in the first TypedDict. | ||
|
||
.. _literal: | ||
|
||
Literal | ||
******* | ||
|
||
.. 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 | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
first argument is True, and Text 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, Text, 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]) -> Text: ... | ||
|
||
# The last overload is a fallback in case the caller | ||
# provides a regular bool: | ||
|
||
@overload | ||
def fetch_data(raw: bool) -> Union[bytes, Text]: ... | ||
|
||
def fetch_data(raw: bool) -> Union[bytes, Text]: | ||
# (Implementation is omitted) | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pass | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
variable_1: Literal[True] = True | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
reveal_type(fetch_data(True)) # Revealed type is 'bytes' | ||
reveal_type(fetch_data(False)) # Revealed type is 'str' | ||
reveal_type(fetch_data(variable_1)) # Revealed type is 'bytes' | ||
|
||
# Variables declared without annotations will continue to have an | ||
# inferred type of 'bool'. | ||
|
||
variable_2 = True | ||
reveal_type(fetch_data(variable_2)) # Revealed type is 'Union[bytes, str]' | ||
|
||
Parameterizing Literals | ||
----------------------- | ||
|
||
Literal types may contain one or more literal bools, ints, strings, and byte | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
strings. 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 can help make writing | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
more complex types involving Literals a little more convenient. | ||
|
||
Literal types may also contain ``None``. Mypy will treat ``Literal[None]`` as being | ||
exactly equivalent to just ``None``. This means that ``Literal[4, None]``, | ||
``Union[Literal[4], None]``, and ``Optional[Literal[4]]`` are all exactly equivalent. | ||
|
||
Literals may also contain aliases of 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: pass | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 Literals in an upcoming release. | ||
|
||
Declaring Literal variables | ||
--------------------------- | ||
|
||
You must explicitly add an annotation to a variable to declare that it is | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 declare the variable to be :ref:`Final <final_attrs>`: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Final variable" is an kind of oxymoron. I would rather say "final name", or reformulate this otherwise. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I decided to rephrase this to say "you can instead change the variable to be Final" (and did a similar thing below). |
||
|
||
.. 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 type checks! | ||
|
||
If we do not provide an explicit type in the Final, the type of ``c`` becomes | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 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 declaring 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. | ||
Michael0x2a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Limitations | ||
----------- | ||
|
||
Mypy will not understand expressions that use Literal variables 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 as a subtype of | ||
``int`` and ``Literal["foo"]`` is treated as a subtype of ``str``. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to double-check, did you want me to lowercase every instance where I use "Literal" as a proper noun?
I capitalized this (and every other time I used the phrase "Literal types") because I wanted to refer specifically to the
Literal[...]
type itself. I can lowercase this if there's a good reason for it, but then we should probably lowercase that phrase every other time I use it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally I would prefer to always use one of two:
literal
or``Literal``
(i.e. if it is capitalized, then it is``code``
).