Skip to content

Commit

Permalink
Minor documentation updates (#12329)
Browse files Browse the repository at this point in the history
* Some minor documentation updates
* Add more discussion of exhaustiveness checking
* Update docs/source/literal_types.rst
* Simplify docs for legacy async

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
  • Loading branch information
JukkaL and JelleZijlstra committed Mar 10, 2022
1 parent 5954bc9 commit 942395a
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 81 deletions.
4 changes: 2 additions & 2 deletions docs/source/config_file.rst
Expand Up @@ -201,7 +201,7 @@ section of the command line docs.

A regular expression that matches file names, directory names and paths
which mypy should ignore while recursively discovering files to check.
Use forward slashes on all platforms.
Use forward slashes (``/``) as directory separators on all platforms.

.. code-block:: ini
Expand Down Expand Up @@ -237,7 +237,7 @@ section of the command line docs.
[tool.mypy]
exclude = [
"^one\.py$", # TOML's double-quoted strings require escaping backslashes
"^one\\.py$", # TOML's double-quoted strings require escaping backslashes
'two\.pyi$', # but TOML's single-quoted strings do not
'^three\.',
]
Expand Down
54 changes: 42 additions & 12 deletions docs/source/literal_types.rst
Expand Up @@ -292,8 +292,8 @@ using ``isinstance()``:
This feature is sometimes called "sum types" or "discriminated union types"
in other programming languages.

Exhaustiveness checks
*********************
Exhaustiveness checking
***********************

You may want to check that some code covers all possible
``Literal`` or ``Enum`` cases. Example:
Expand Down Expand Up @@ -359,6 +359,35 @@ mypy will spot the error:
# expected "NoReturn"
assert_never(x)
If runtime checking against unexpected values is not needed, you can
leave out the ``assert_never`` call in the above example, and mypy
will still generate an error about function ``validate`` returning
without a value:

.. code-block:: python
PossibleValues = Literal['one', 'two', 'three']
# Error: Missing return statement
def validate(x: PossibleValues) -> bool:
if x == 'one':
return True
elif x == 'two':
return False
Exhaustiveness checking is also supported for match statements (Python 3.10 and later):

.. code-block:: python
def validate(x: PossibleValues) -> bool:
match x:
case 'one':
return True
case 'two':
return False
assert_never(x)
Limitations
***********

Expand Down Expand Up @@ -404,10 +433,10 @@ You can use enums to annotate types as you would expect:
Movement(Direction.up, 5.0) # ok
Movement('up', 5.0) # E: Argument 1 to "Movemement" has incompatible type "str"; expected "Direction"
Exhaustive checks
*****************
Exhaustiveness checking
***********************

Similiar to ``Literal`` types ``Enum`` supports exhaustive checks.
Similar to ``Literal`` types, ``Enum`` supports exhaustiveness checking.
Let's start with a definition:

.. code-block:: python
Expand All @@ -423,21 +452,22 @@ Let's start with a definition:
up = 'up'
down = 'down'
Now, let's define an exhaustive check:
Now, let's use an exhaustiveness check:

.. code-block:: python
def choose_direction(direction: Direction) -> None:
if direction is Direction.up:
reveal_type(direction) # N: Revealed type is "Literal[ex.Direction.up]"
reveal_type(direction) # N: Revealed type is "Literal[Direction.up]"
print('Going up!')
return
elif direction is Direction.down:
print('Down')
return
# This line is never reached
assert_never(direction)
And then test that it raises an error when some cases are not covered:
If we forget to handle one of the cases, mypy will generate an error:

.. code-block:: python
Expand All @@ -447,13 +477,13 @@ And then test that it raises an error when some cases are not covered:
return
assert_never(direction) # E: Argument 1 to "assert_never" has incompatible type "Direction"; expected "NoReturn"
Exhaustiveness checking is also supported for match statements (Python 3.10 and later).

Extra Enum checks
*****************

Mypy also tries to support special features of ``Enum``
the same way Python's runtime does.

Extra checks:
the same way Python's runtime does:

- Any ``Enum`` class with values is implicitly :ref:`final <final_attrs>`.
This is what happens in CPython:
Expand All @@ -467,7 +497,7 @@ Extra checks:
...
TypeError: Other: cannot extend enumeration 'Some'
We do the same thing:
Mypy also catches this error:

.. code-block:: python
Expand Down
90 changes: 23 additions & 67 deletions docs/source/more_types.rst
Expand Up @@ -874,68 +874,6 @@ value of type :py:class:`Coroutine[Any, Any, T] <typing.Coroutine>`, which is a
:ref:`reveal_type() <reveal-type>` displays the inferred static type of
an expression.

If you want to use coroutines in Python 3.4, which does not support
the ``async def`` syntax, you can instead use the :py:func:`@asyncio.coroutine <asyncio.coroutine>`
decorator to convert a generator into a coroutine.

Note that we set the ``YieldType`` of the generator to be ``Any`` in the
following example. This is because the exact yield type is an implementation
detail of the coroutine runner (e.g. the :py:mod:`asyncio` event loop) and your
coroutine shouldn't have to know or care about what precisely that type is.

.. code-block:: python
from typing import Any, Generator
import asyncio
@asyncio.coroutine
def countdown_2(tag: str, count: int) -> Generator[Any, None, str]:
while count > 0:
print('T-minus {} ({})'.format(count, tag))
yield from asyncio.sleep(0.1)
count -= 1
return "Blastoff!"
loop = asyncio.get_event_loop()
loop.run_until_complete(countdown_2("USS Enterprise", 5))
loop.close()
As before, the result of calling a generator decorated with :py:func:`@asyncio.coroutine <asyncio.coroutine>`
will be a value of type :py:class:`Awaitable[T] <typing.Awaitable>`.

.. note::

At runtime, you are allowed to add the :py:func:`@asyncio.coroutine <asyncio.coroutine>` decorator to
both functions and generators. This is useful when you want to mark a
work-in-progress function as a coroutine, but have not yet added ``yield`` or
``yield from`` statements:

.. code-block:: python
import asyncio
@asyncio.coroutine
def serialize(obj: object) -> str:
# todo: add yield/yield from to turn this into a generator
return "placeholder"
However, mypy currently does not support converting functions into
coroutines. Support for this feature will be added in a future version, but
for now, you can manually force the function to be a generator by doing
something like this:

.. code-block:: python
from typing import Generator
import asyncio
@asyncio.coroutine
def serialize(obj: object) -> Generator[None, None, str]:
# todo: add yield/yield from to turn this into a generator
if False:
yield
return "placeholder"
You may also choose to create a subclass of :py:class:`~typing.Awaitable` instead:

.. code-block:: python
Expand Down Expand Up @@ -995,11 +933,29 @@ To create an iterable coroutine, subclass :py:class:`~typing.AsyncIterator`:
loop.run_until_complete(countdown_4("Serenity", 5))
loop.close()
For a more concrete example, the mypy repo has a toy webcrawler that
demonstrates how to work with coroutines. One version
`uses async/await <https://github.com/python/mypy/blob/master/test-data/samples/crawl2.py>`_
and one
`uses yield from <https://github.com/python/mypy/blob/master/test-data/samples/crawl.py>`_.
If you use coroutines in legacy code that was originally written for
Python 3.4, which did not support the ``async def`` syntax, you would
instead use the :py:func:`@asyncio.coroutine <asyncio.coroutine>`
decorator to convert a generator into a coroutine, and use a
generator type as the return type:

.. code-block:: python
from typing import Any, Generator
import asyncio
@asyncio.coroutine
def countdown_2(tag: str, count: int) -> Generator[Any, None, str]:
while count > 0:
print('T-minus {} ({})'.format(count, tag))
yield from asyncio.sleep(0.1)
count -= 1
return "Blastoff!"
loop = asyncio.get_event_loop()
loop.run_until_complete(countdown_2("USS Enterprise", 5))
loop.close()
.. _typeddict:

Expand Down

0 comments on commit 942395a

Please sign in to comment.