From bbf643f38ce8bce6b55021fab72deafd341ef24d Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Sat, 21 May 2022 17:35:39 +0200 Subject: [PATCH] Split Best Practics document from Stubs document Move all "style guide" items over that apply to both stubs as well as implementation. The items are reordered and the "Types" section was split, but the text itself is unchanged. Part of #851 --- docs/index.rst | 2 + docs/source/best_practices.rst | 130 +++++++++++++++++++++++++++++++++ docs/source/reference.rst | 1 + docs/source/stubs.rst | 97 +----------------------- 4 files changed, 136 insertions(+), 94 deletions(-) create mode 100644 docs/source/best_practices.rst diff --git a/docs/index.rst b/docs/index.rst index b5fe2688..d1bcea2c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,6 +32,8 @@ Indices and tables * :ref:`genindex` * :ref:`search` +.. _contact: + Discussions and Support ======================= diff --git a/docs/source/best_practices.rst b/docs/source/best_practices.rst new file mode 100644 index 00000000..f40c447c --- /dev/null +++ b/docs/source/best_practices.rst @@ -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: ... diff --git a/docs/source/reference.rst b/docs/source/reference.rst index c7fc57ee..814dfe1c 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -7,6 +7,7 @@ Type System Reference :caption: Contents: stubs + best_practices quality typing Module Documentation diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index 215c1a31..b0db6109 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -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. @@ -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 ----------------------- @@ -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 ------- @@ -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::