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::