From 0a629be1dc3a3061e3aa23248b7b424e93d8eea8 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 10 Mar 2022 16:11:02 +0000 Subject: [PATCH 1/4] Some minor documentation updates --- docs/source/config_file.rst | 4 ++-- docs/source/literal_types.rst | 23 +++++++++++------------ docs/source/more_types.rst | 24 ++++++++++-------------- 3 files changed, 23 insertions(+), 28 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..ea55af8d8db1 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: @@ -404,10 +404,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 checks +********************* -Similiar to ``Literal`` types ``Enum`` supports exhaustive checks. +Similiar to ``Literal`` types, ``Enum`` supports exhaustiveness checks. Let's start with a definition: .. code-block:: python @@ -423,21 +423,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 @@ -451,9 +452,7 @@ 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 +466,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..06b9c6ddbe31 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -874,14 +874,16 @@ 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. +If you use coroutines in code that was written for Python 3.4, which +does not support the ``async def`` syntax, you would 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. +Note that we set the first type argument (``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 @@ -920,7 +922,7 @@ will be a value of type :py:class:`Awaitable[T] `. return "placeholder" However, mypy currently does not support converting functions into - coroutines. Support for this feature will be added in a future version, but + coroutines. Support for this feature may be added in a future version, but for now, you can manually force the function to be a generator by doing something like this: @@ -995,12 +997,6 @@ 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 `_. - .. _typeddict: TypedDict From b44a533930bad70d70406b25e312f11b7c3cf9ee Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 10 Mar 2022 16:23:04 +0000 Subject: [PATCH 2/4] Add more discussion of exhaustiveness checking --- docs/source/literal_types.rst | 37 ++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index ea55af8d8db1..fb1893187a54 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -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" -Exhaustiveness checks -********************* +Exhaustiveness checking +*********************** -Similiar to ``Literal`` types, ``Enum`` supports exhaustiveness checks. +Similiar to ``Literal`` types, ``Enum`` supports exhaustiveness checking. Let's start with a definition: .. code-block:: python @@ -448,6 +477,8 @@ If we forget to handle one of the cases, mypy will generate an error: 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 ***************** From f96dd7c3661f0e2a69fd870a03632c3ff55f5c7f Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 10 Mar 2022 16:37:36 +0000 Subject: [PATCH 3/4] Update docs/source/literal_types.rst Co-authored-by: Jelle Zijlstra --- docs/source/literal_types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index fb1893187a54..2bca6db71ac7 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -436,7 +436,7 @@ You can use enums to annotate types as you would expect: Exhaustiveness checking *********************** -Similiar to ``Literal`` types, ``Enum`` supports exhaustiveness checking. +Similar to ``Literal`` types, ``Enum`` supports exhaustiveness checking. Let's start with a definition: .. code-block:: python From f75f6aabb1919c1f68785e5d9ccc90a8f49fc11a Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 10 Mar 2022 16:47:57 +0000 Subject: [PATCH 4/4] Simplify docs for legacy async --- docs/source/more_types.rst | 88 +++++++++++--------------------------- 1 file changed, 24 insertions(+), 64 deletions(-) diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 06b9c6ddbe31..dd688cab7e17 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -874,70 +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 use coroutines in code that was written for Python 3.4, which -does not support the ``async def`` syntax, you would instead use the -:py:func:`@asyncio.coroutine ` decorator to convert -a generator into a coroutine. - -Note that we set the first type argument (``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 may 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 @@ -997,6 +933,30 @@ To create an iterable coroutine, subclass :py:class:`~typing.AsyncIterator`: loop.run_until_complete(countdown_4("Serenity", 5)) loop.close() +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: TypedDict