Skip to content

Commit

Permalink
Improvements to cheat sheet (#14972)
Browse files Browse the repository at this point in the history
Everyone can use future annotations now. Also forward references are the
most type hinting popular question on Stack Overflow, so give a little
more detail.

The IO example should go with standard duck types.

I moved the async example to the bottom. It basically just works as
you'd expect, maybe we should remove it altogether.

Linking #13681

---------

Co-authored-by: Ethan Smith <ethan@ethanhs.me>
  • Loading branch information
hauntsaninja and ethanhs authored Mar 30, 2023
1 parent 7c49fa6 commit 10ae912
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 38 deletions.
89 changes: 51 additions & 38 deletions docs/source/cheat_sheet_py3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ See :ref:`type-inference-and-annotations` for more details.
# You don't need to initialize a variable to annotate it
a: int # Ok (no value at runtime until assigned)
# Doing so is useful in conditional branches
# Doing so can be useful in conditional branches
child: bool
if age < 18:
child = True
Expand All @@ -34,7 +34,7 @@ Useful built-in types

.. code-block:: python
# For most types, just use the name of the type.
# For most types, just use the name of the type in the annotation
# Note that mypy can usually infer the type of a variable from its value,
# so technically these annotations are redundant
x: int = 1
Expand Down Expand Up @@ -75,10 +75,11 @@ Useful built-in types
# Use Optional[X] for a value that could be None
# Optional[X] is the same as X | None or Union[X, None]
x: Optional[str] = "something" if some_condition() else None
# Mypy understands a value can't be None in an if-statement
if x is not None:
# Mypy understands x won't be None here because of the if-statement
print(x.upper())
# If a value can never be None due to some invariants, use an assert
# If you know a value can never be None due to some logic that mypy doesn't
# understand, use an assert
assert x is not None
print(x.upper())
Expand Down Expand Up @@ -259,6 +260,8 @@ When you're puzzled or when things are complicated
In some cases type annotations can cause issues at runtime, see
:ref:`runtime_troubles` for dealing with this.

See :ref:`silencing-type-errors` for details on how to silence errors.

Standard "duck types"
*********************

Expand Down Expand Up @@ -294,37 +297,11 @@ that are common in idiomatic Python are standardized.
f({3: 'yes', 4: 'no'})
You can even make your own duck types using :ref:`protocol-types`.

Coroutines and asyncio
**********************

See :ref:`async-and-await` for the full detail on typing coroutines and asynchronous code.

.. code-block:: python
import asyncio
# A coroutine is typed like a normal function
async def countdown35(tag: str, count: int) -> str:
while count > 0:
print(f'T-minus {count} ({tag})')
await asyncio.sleep(0.1)
count -= 1
return "Blastoff!"
Miscellaneous
*************

.. code-block:: python
import sys
from typing import IO
# Use IO[] for functions that should accept or return any
# object that comes from an open() call (IO[] does not
# Use IO[str] or IO[bytes] for functions that should accept or return
# objects that come from an open() call (note that IO does not
# distinguish between reading, writing or other modes)
def get_sys_IO(mode: str = 'w') -> IO[str]:
if mode == 'w':
Expand All @@ -334,19 +311,38 @@ Miscellaneous
else:
return sys.stdout
# Forward references are useful if you want to reference a class before
# it is defined
You can even make your own duck types using :ref:`protocol-types`.

Forward references
******************

.. code-block:: python
# You may want to reference a class before it is defined.
# This is known as a "forward reference".
def f(foo: A) -> int: # This will fail at runtime with 'A' is not defined
...
class A:
# However, if you add the following special import:
from __future__ import annotations
# It will work at runtime and type checking will succeed as long as there
# is a class of that name later on in the file
def f(foo: A) -> int: # Ok
...
# If you use the string literal 'A', it will pass as long as there is a
# class of that name later on in the file
def f(foo: 'A') -> int: # Ok
# Another option is to just put the type in quotes
def f(foo: 'A') -> int: # Also ok
...
class A:
# This can also come up if you need to reference a class in a type
# annotation inside the definition of that class
@classmethod
def create(cls) -> A:
...
See :ref:`forward-references` for more details.

Decorators
**********
Expand All @@ -365,3 +361,20 @@ Decorator functions can be expressed via generics. See
def decorator_args(url: str) -> Callable[[F], F]:
...
Coroutines and asyncio
**********************

See :ref:`async-and-await` for the full detail on typing coroutines and asynchronous code.

.. code-block:: python
import asyncio
# A coroutine is typed like a normal function
async def countdown(tag: str, count: int) -> str:
while count > 0:
print(f'T-minus {count} ({tag})')
await asyncio.sleep(0.1)
count -= 1
return "Blastoff!"
2 changes: 2 additions & 0 deletions docs/source/runtime_troubles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ Since code inside ``if TYPE_CHECKING:`` is not executed at runtime, it provides
a convenient way to tell mypy something without the code being evaluated at
runtime. This is most useful for resolving :ref:`import cycles <import-cycles>`.

.. _forward-references:

Class name forward references
-----------------------------

Expand Down

0 comments on commit 10ae912

Please sign in to comment.