-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Update docs for Literal types #8152
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
Merged
Michael0x2a
merged 3 commits into
python:master
from
Michael0x2a:adjust-docs-for-literal-types
Jan 8, 2020
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,6 @@ | |
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 | ||
|
@@ -23,8 +16,7 @@ precise type signature for this function using ``Literal[...]`` and overloads: | |
|
||
.. code-block:: python | ||
|
||
from typing import overload, Union | ||
from typing_extensions import Literal | ||
from typing import overload, Union, Literal | ||
|
||
# The first two overloads use Literal[...] so we can | ||
# have precise return types: | ||
|
@@ -53,18 +45,25 @@ precise type signature for this function using ``Literal[...]`` and overloads: | |
variable = True | ||
reveal_type(fetch_data(variable)) # Revealed type is 'Union[bytes, str]' | ||
|
||
.. note:: | ||
|
||
The examples in this page import ``Literal`` as well as ``Final`` and | ||
``TypedDict`` from the ``typing`` module. These types were added to | ||
``typing`` in Python 3.8, but are also available for use in Python 2.7 | ||
and 3.4 - 3.7 via the ``typing_extensions`` package. | ||
|
||
Parameterizing Literals | ||
*********************** | ||
|
||
Literal types may contain one or more literal bools, ints, strs, and bytes. | ||
However, literal types **cannot** contain arbitrary expressions: | ||
Literal types may contain one or more literal bools, ints, strs, bytes, and | ||
enum values. 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. | ||
So, ``Literal[-3, b"foo", MyEnum.A]`` is equivalent to | ||
``Union[Literal[-3], Literal[b"foo"], Literal[MyEnum.A]]``. 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]``, | ||
|
@@ -88,9 +87,6 @@ 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 | ||
*************************** | ||
|
||
|
@@ -115,7 +111,7 @@ you can instead change the variable to be ``Final`` (see :ref:`final_attrs`): | |
|
||
.. code-block:: python | ||
|
||
from typing_extensions import Final, Literal | ||
from typing import Final, Literal | ||
|
||
def expects_literal(x: Literal[19]) -> None: pass | ||
|
||
|
@@ -134,7 +130,7 @@ For example, mypy will type check the above program almost as if it were written | |
|
||
.. code-block:: python | ||
|
||
from typing_extensions import Final, Literal | ||
from typing import Final, Literal | ||
|
||
def expects_literal(x: Literal[19]) -> None: pass | ||
|
||
|
@@ -151,7 +147,7 @@ For example, compare and contrast what happens when you try appending these type | |
|
||
.. code-block:: python | ||
|
||
from typing_extensions import Final, Literal | ||
from typing import Final, Literal | ||
|
||
a: Final = 19 | ||
b: Literal[19] = 19 | ||
|
@@ -168,6 +164,131 @@ For example, compare and contrast what happens when you try appending these type | |
reveal_type(list_of_lits) # Revealed type is 'List[Literal[19]]' | ||
|
||
|
||
Intelligent indexing | ||
******************** | ||
|
||
We can use Literal types to more precisely index into structured heterogeneous | ||
types such as tuples, NamedTuples, and TypedDicts. This feature is known as | ||
*intelligent indexing*. | ||
|
||
For example, when we index into a tuple using some int, the inferred type is | ||
normally the union of the tuple item types. However, if we want just the type | ||
corresponding to some particular index, we can use Literal types like so: | ||
|
||
.. code-block:: python | ||
|
||
from typing import TypedDict | ||
|
||
tup = ("foo", 3.4) | ||
|
||
# Indexing with an int literal gives us the exact type for that index | ||
reveal_type(tup[0]) # Revealed type is 'str' | ||
|
||
# But what if we want the index to be a variable? Normally mypy won't | ||
# know exactly what the index is and so will return a less precise type: | ||
int_index = 1 | ||
reveal_type(tup[int_index]) # Revealed type is 'Union[str, float]' | ||
|
||
# But if we use either Literal types or a Final int, we can gain back | ||
# the precision we originally had: | ||
lit_index: Literal[1] = 1 | ||
fin_index: Final = 1 | ||
reveal_type(tup[lit_index]) # Revealed type is 'str' | ||
reveal_type(tup[fin_index]) # Revealed type is 'str' | ||
|
||
# We can do the same thing with with TypedDict and str keys: | ||
class MyDict(TypedDict): | ||
name: str | ||
main_id: int | ||
backup_id: int | ||
|
||
d: MyDict = {"name": "Saanvi", "main_id": 111, "backup_id": 222} | ||
name_key: Final = "name" | ||
reveal_type(d[name_key]) # Revealed type is 'str' | ||
|
||
# You can also index using unions of literals | ||
id_key: Literal["main_id", "backup_id"] | ||
reveal_type(d[id_key]) # Revealed type is 'int' | ||
|
||
.. _tagged_unions: | ||
|
||
Tagged unions | ||
************* | ||
|
||
When you have a union of types, you can normally discriminate between each type | ||
in the union by using ``isinstance`` checks. For example, if you had a variable ``x`` of | ||
type ``Union[int, str]``, you could write some code that runs only if ``x`` is an int | ||
by doing ``if isinstance(x, int): ...``. | ||
|
||
However, it is not always possible or convenient to do this. For example, it is not | ||
possible to use ``isinstance`` to distinguish between two different TypedDicts since | ||
at runtime, your variable will simply be just a dict. | ||
|
||
Instead, what you can do is *label* or *tag* your TypedDicts with a distinct Literal | ||
type. Then, you can discriminate between each kind of TypedDict by checking the label: | ||
|
||
.. code-block:: python | ||
|
||
from typing import Literal, TypedDict, Union | ||
|
||
class NewJobEvent(TypedDict): | ||
tag: Literal["new-job"] | ||
job_name: str | ||
config_file_path: str | ||
|
||
class CancelJobEvent(TypedDict): | ||
tag: Literal["cancel-job"] | ||
job_id: int | ||
|
||
Event = Union[NewJobEvent, CancelJobEvent] | ||
|
||
def process_event(event: Event) -> None: | ||
# Since we made sure both TypedDicts have a key named 'tag', it's | ||
# safe to do 'event["tag"]'. This expression normally has the type | ||
# Literal["new-job", "cancel-job"], but the check below will narrow | ||
# the type to either Literal["new-job"] or Literal["cancel-job"]. | ||
# | ||
# This in turns narrows the type of 'event' to either NewJobEvent | ||
# or CancelJobEvent. | ||
if event["tag"] == "new-job": | ||
print(event["job_name"]) | ||
else: | ||
print(event["job_id"]) | ||
|
||
While this feature is mostly useful when working with TypedDicts, you can also | ||
use the same technique wih regular objects, tuples, or namedtuples. | ||
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. Mention that tags can also be proper enum values (using |
||
|
||
Similarly, tags do not need to be specifically str Literals: they can be any type | ||
you can normally narrow within ``if`` statements and the like. For example, you | ||
could have your tags be int or Enum Literals or even regular classes you narrow | ||
using ``isinstance()``: | ||
|
||
.. code-block:: python | ||
|
||
from typing import Generic, TypeVar, Union | ||
|
||
T = TypeVar('T') | ||
|
||
class Wrapper(Generic[T]): | ||
def __init__(self, inner: T) -> None: | ||
self.inner = inner | ||
|
||
def process(w: Union[Wrapper[int], Wrapper[str]]) -> None: | ||
# Doing `if isinstance(w, Wrapper[int])` does not work: isinstance requires | ||
# that the second argument always be an *erased* type, with no generics. | ||
# This is because generics are a typing-only concept and do not exist at | ||
# runtime in a way `isinstance` can always check. | ||
# | ||
# However, we can side-step this by checking the type of `w.inner` to | ||
# narrow `w` itself: | ||
if isinstance(w.inner, int): | ||
reveal_type(w) # Revealed type is 'Wrapper[int]' | ||
else: | ||
reveal_type(w) # Revealed type is 'Wrapper[str]' | ||
|
||
This feature is sometimes called "sum types" or "discriminated union types" | ||
in other programming languages. | ||
|
||
Limitations | ||
*********** | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Maybe add an example with TypedDict? Maybe also an example with union of literals, something along these lines: