From 0d38613d8bd156a793a2f9f0f8f331499ad31531 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 10 Mar 2022 17:45:41 +0000 Subject: [PATCH] Minor documentation updates (#12329) * 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 --- docs/source/config_file.rst | 4 +- docs/source/literal_types.rst | 54 ++++++++++++++++----- docs/source/more_types.rst | 90 +++++++++-------------------------- 3 files changed, 67 insertions(+), 81 deletions(-) diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 0b53f1ca5370..6373a5094b3d 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -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 @@ -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\.', ] diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index c5df354ce678..2bca6db71ac7 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -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: @@ -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 *********** @@ -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 @@ -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 @@ -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 `. This is what happens in CPython: @@ -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 diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 2203022ad73f..dd688cab7e17 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -874,68 +874,6 @@ value of type :py:class:`Coroutine[Any, Any, T] `, which is a :ref:`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 ` -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 ` -will be a value of type :py:class:`Awaitable[T] `. - -.. note:: - - At runtime, you are allowed to add the :py:func:`@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 @@ -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 `_ -and one -`uses yield from `_. +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 ` +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: