Skip to content

Split Best Practics document from Stubs document #1193

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
merged 1 commit into from
May 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Indices and tables
* :ref:`genindex`
* :ref:`search`

.. _contact:

Discussions and Support
=======================

Expand Down
130 changes: 130 additions & 0 deletions docs/source/best_practices.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
.. _best-practices:

*********************
Typing Best Practices
*********************

Introduction
============

Over time, some best practices have proven themselves as useful when working
with type hints in Python. Not all practices are applicable in all situations
and some practices come down to personal style and preference, but they
are a good default set of recommendations to fall back to, unless there is
a specific reason to deviate.

These best practices are constantly evolving, especially as the typing
capabilities and ecosystem grow. So expect new best practices to be added
and existing best practices to be modified or even removed as better practices
evolve. That is why we would love to hear from your experiences with typing.
Please see :ref:`contact` on how to join the discussion.

Typing Features
===============

Type Aliases
------------

Use ``TypeAlias`` for type aliases (but not for regular aliases).

Yes::

_IntList: TypeAlias = list[int]
g = os.stat
Path = pathlib.Path
ERROR = errno.EEXIST

No::

_IntList = list[int]
g: TypeAlias = os.stat
Path: TypeAlias = pathlib.Path
ERROR: TypeAlias = errno.EEXIST

Ergonomic Practices
===================

Using `Any`
-----------

Generally, use ``Any`` when a type cannot be expressed appropriately
with the current type system or using the correct type is unergonomic.

Arguments and Return Types
--------------------------

For arguments, prefer protocols and abstract types (``Mapping``,
``Sequence``, ``Iterable``, etc.). If an argument accepts literally any value,
use ``object`` instead of ``Any``.

For return values, prefer concrete types (``list``, ``dict``, etc.) for
concrete implementations. The return values of protocols
and abstract base classes must be judged on a case-by-case basis.

Yes::

def map_it(input: Iterable[str]) -> list[int]: ...
def create_map() -> dict[str, int]: ...
def to_string(o: object) -> str: ... # accepts any object

No::

def map_it(input: list[str]) -> list[int]: ...
def create_map() -> MutableMapping[str, int]: ...
def to_string(o: Any) -> str: ...

Maybe::

class MyProto(Protocol):
def foo(self) -> list[int]: ...
def bar(self) -> Mapping[str]: ...

Avoid union return types, since they require ``isinstance()`` checks.
Use ``Any`` or ``X | Any`` if necessary.

Stylistic Practices
===================

Shorthand Syntax
----------------

Where possible, use shorthand syntax for unions instead of
``Union`` or ``Optional``. ``None`` should be the last
element of an union.

Yes::

def foo(x: str | int) -> None: ...
def bar(x: str | None) -> int | None: ...

No::

def foo(x: Union[str, int]) -> None: ...
def bar(x: Optional[str]) -> Optional[int]: ...
def baz(x: None | str) -> None: ...

Types
-----

Use ``float`` instead of ``int | float``.
Use ``None`` instead of ``Literal[None]``.

Built-in Generics
-----------------

Use built-in generics instead of the aliases from ``typing``,
where possible.

Yes::

from collections.abc import Iterable

def foo(x: type[MyClass]) -> list[str]: ...
def bar(x: Iterable[str]) -> None: ...

No::

from typing import Iterable, List, Type

def foo(x: Type[MyClass]) -> List[str]: ...
def bar(x: Iterable[str]) -> None: ...
1 change: 1 addition & 0 deletions docs/source/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Type System Reference
:caption: Contents:

stubs
best_practices
quality
typing Module Documentation <https://docs.python.org/3/library/typing.html>

Expand Down
97 changes: 3 additions & 94 deletions docs/source/stubs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ should not reject stubs that do not follow these recommendations, but
linters can warn about them.

Stub files should generally follow the Style Guide for Python Code (PEP 8)
[#pep8]_. There are a few exceptions, outlined below, that take the
[#pep8]_ and the :ref:`best-practices`. There are a few exceptions, outlined below, that take the
different structure of stub files into account and are aimed to create
more concise files.

Expand Down Expand Up @@ -810,24 +810,6 @@ No::
x: int
class MyError(Exception): ... # leave an empty line between the classes

Shorthand Syntax
----------------

Where possible, use shorthand syntax for unions instead of
``Union`` or ``Optional``. ``None`` should be the last
element of an union.

Yes::

def foo(x: str | int) -> None: ...
def bar(x: str | None) -> int | None: ...

No::

def foo(x: Union[str, int]) -> None: ...
def bar(x: Optional[str]) -> Optional[int]: ...
def baz(x: None | str) -> None: ...

Module Level Attributes
-----------------------

Expand All @@ -846,24 +828,7 @@ No::
z = 0 # type: int
a = ... # type: int

Type Aliases
------------

Use ``TypeAlias`` for type aliases (but not for regular aliases).

Yes::

_IntList: TypeAlias = list[int]
g = os.stat
Path = pathlib.Path
ERROR = errno.EEXIST

No::

_IntList = list[int]
g: TypeAlias = os.stat
Path: TypeAlias = pathlib.Path
ERROR: TypeAlias = errno.EEXIST
.. _stub-style-classes:

Classes
-------
Expand Down Expand Up @@ -998,67 +963,11 @@ No::
forward_reference: 'OtherClass'
class OtherClass: ...

Types
-----

Generally, use ``Any`` when a type cannot be expressed appropriately
with the current type system or using the correct type is unergonomic.

Use ``float`` instead of ``int | float``.
Use ``None`` instead of ``Literal[None]``.

For arguments, prefer protocols and abstract types (``Mapping``,
``Sequence``, ``Iterable``, etc.). If an argument accepts literally any value,
use ``object`` instead of ``Any``.

For return values, prefer concrete types (``list``, ``dict``, etc.) for
concrete implementations. The return values of protocols
and abstract base classes must be judged on a case-by-case basis.

Yes::

def map_it(input: Iterable[str]) -> list[int]: ...
def create_map() -> dict[str, int]: ...
def to_string(o: object) -> str: ... # accepts any object

No::

def map_it(input: list[str]) -> list[int]: ...
def create_map() -> MutableMapping[str, int]: ...
def to_string(o: Any) -> str: ...

Maybe::

class MyProto(Protocol):
def foo(self) -> list[int]: ...
def bar(self) -> Mapping[str]: ...

Avoid union return types, since they require ``isinstance()`` checks.
Use ``Any`` or ``X | Any`` if necessary.

Use built-in generics instead of the aliases from ``typing``,
where possible. See the section `Built-in Generics`_ for cases,
where it's not possible to use them.

Yes::

from collections.abc import Iterable

def foo(x: type[MyClass]) -> list[str]: ...
def bar(x: Iterable[str]) -> None: ...

No::

from typing import Iterable, List, Type

def foo(x: Type[MyClass]) -> List[str]: ...
def bar(x: Iterable[str]) -> None: ...

NamedTuple and TypedDict
------------------------

Use the class-based syntax for ``typing.NamedTuple`` and
``typing.TypedDict``, following the Classes section of this style guide.
``typing.TypedDict``, following the :ref:`stub-style-classes` section of this style guide.

Yes::

Expand Down