Skip to content

Commit 831742f

Browse files
authored
Split Best Practics document from Stubs document (#1193)
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
1 parent bc18c21 commit 831742f

File tree

4 files changed

+136
-94
lines changed

4 files changed

+136
-94
lines changed

docs/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Indices and tables
3232
* :ref:`genindex`
3333
* :ref:`search`
3434

35+
.. _contact:
36+
3537
Discussions and Support
3638
=======================
3739

docs/source/best_practices.rst

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
.. _best-practices:
2+
3+
*********************
4+
Typing Best Practices
5+
*********************
6+
7+
Introduction
8+
============
9+
10+
Over time, some best practices have proven themselves as useful when working
11+
with type hints in Python. Not all practices are applicable in all situations
12+
and some practices come down to personal style and preference, but they
13+
are a good default set of recommendations to fall back to, unless there is
14+
a specific reason to deviate.
15+
16+
These best practices are constantly evolving, especially as the typing
17+
capabilities and ecosystem grow. So expect new best practices to be added
18+
and existing best practices to be modified or even removed as better practices
19+
evolve. That is why we would love to hear from your experiences with typing.
20+
Please see :ref:`contact` on how to join the discussion.
21+
22+
Typing Features
23+
===============
24+
25+
Type Aliases
26+
------------
27+
28+
Use ``TypeAlias`` for type aliases (but not for regular aliases).
29+
30+
Yes::
31+
32+
_IntList: TypeAlias = list[int]
33+
g = os.stat
34+
Path = pathlib.Path
35+
ERROR = errno.EEXIST
36+
37+
No::
38+
39+
_IntList = list[int]
40+
g: TypeAlias = os.stat
41+
Path: TypeAlias = pathlib.Path
42+
ERROR: TypeAlias = errno.EEXIST
43+
44+
Ergonomic Practices
45+
===================
46+
47+
Using `Any`
48+
-----------
49+
50+
Generally, use ``Any`` when a type cannot be expressed appropriately
51+
with the current type system or using the correct type is unergonomic.
52+
53+
Arguments and Return Types
54+
--------------------------
55+
56+
For arguments, prefer protocols and abstract types (``Mapping``,
57+
``Sequence``, ``Iterable``, etc.). If an argument accepts literally any value,
58+
use ``object`` instead of ``Any``.
59+
60+
For return values, prefer concrete types (``list``, ``dict``, etc.) for
61+
concrete implementations. The return values of protocols
62+
and abstract base classes must be judged on a case-by-case basis.
63+
64+
Yes::
65+
66+
def map_it(input: Iterable[str]) -> list[int]: ...
67+
def create_map() -> dict[str, int]: ...
68+
def to_string(o: object) -> str: ... # accepts any object
69+
70+
No::
71+
72+
def map_it(input: list[str]) -> list[int]: ...
73+
def create_map() -> MutableMapping[str, int]: ...
74+
def to_string(o: Any) -> str: ...
75+
76+
Maybe::
77+
78+
class MyProto(Protocol):
79+
def foo(self) -> list[int]: ...
80+
def bar(self) -> Mapping[str]: ...
81+
82+
Avoid union return types, since they require ``isinstance()`` checks.
83+
Use ``Any`` or ``X | Any`` if necessary.
84+
85+
Stylistic Practices
86+
===================
87+
88+
Shorthand Syntax
89+
----------------
90+
91+
Where possible, use shorthand syntax for unions instead of
92+
``Union`` or ``Optional``. ``None`` should be the last
93+
element of an union.
94+
95+
Yes::
96+
97+
def foo(x: str | int) -> None: ...
98+
def bar(x: str | None) -> int | None: ...
99+
100+
No::
101+
102+
def foo(x: Union[str, int]) -> None: ...
103+
def bar(x: Optional[str]) -> Optional[int]: ...
104+
def baz(x: None | str) -> None: ...
105+
106+
Types
107+
-----
108+
109+
Use ``float`` instead of ``int | float``.
110+
Use ``None`` instead of ``Literal[None]``.
111+
112+
Built-in Generics
113+
-----------------
114+
115+
Use built-in generics instead of the aliases from ``typing``,
116+
where possible.
117+
118+
Yes::
119+
120+
from collections.abc import Iterable
121+
122+
def foo(x: type[MyClass]) -> list[str]: ...
123+
def bar(x: Iterable[str]) -> None: ...
124+
125+
No::
126+
127+
from typing import Iterable, List, Type
128+
129+
def foo(x: Type[MyClass]) -> List[str]: ...
130+
def bar(x: Iterable[str]) -> None: ...

docs/source/reference.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Type System Reference
77
:caption: Contents:
88

99
stubs
10+
best_practices
1011
quality
1112
typing Module Documentation <https://docs.python.org/3/library/typing.html>
1213

docs/source/stubs.rst

Lines changed: 3 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ should not reject stubs that do not follow these recommendations, but
766766
linters can warn about them.
767767

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

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

813-
Shorthand Syntax
814-
----------------
815-
816-
Where possible, use shorthand syntax for unions instead of
817-
``Union`` or ``Optional``. ``None`` should be the last
818-
element of an union.
819-
820-
Yes::
821-
822-
def foo(x: str | int) -> None: ...
823-
def bar(x: str | None) -> int | None: ...
824-
825-
No::
826-
827-
def foo(x: Union[str, int]) -> None: ...
828-
def bar(x: Optional[str]) -> Optional[int]: ...
829-
def baz(x: None | str) -> None: ...
830-
831813
Module Level Attributes
832814
-----------------------
833815

@@ -846,24 +828,7 @@ No::
846828
z = 0 # type: int
847829
a = ... # type: int
848830

849-
Type Aliases
850-
------------
851-
852-
Use ``TypeAlias`` for type aliases (but not for regular aliases).
853-
854-
Yes::
855-
856-
_IntList: TypeAlias = list[int]
857-
g = os.stat
858-
Path = pathlib.Path
859-
ERROR = errno.EEXIST
860-
861-
No::
862-
863-
_IntList = list[int]
864-
g: TypeAlias = os.stat
865-
Path: TypeAlias = pathlib.Path
866-
ERROR: TypeAlias = errno.EEXIST
831+
.. _stub-style-classes:
867832

868833
Classes
869834
-------
@@ -998,67 +963,11 @@ No::
998963
forward_reference: 'OtherClass'
999964
class OtherClass: ...
1000965

1001-
Types
1002-
-----
1003-
1004-
Generally, use ``Any`` when a type cannot be expressed appropriately
1005-
with the current type system or using the correct type is unergonomic.
1006-
1007-
Use ``float`` instead of ``int | float``.
1008-
Use ``None`` instead of ``Literal[None]``.
1009-
1010-
For arguments, prefer protocols and abstract types (``Mapping``,
1011-
``Sequence``, ``Iterable``, etc.). If an argument accepts literally any value,
1012-
use ``object`` instead of ``Any``.
1013-
1014-
For return values, prefer concrete types (``list``, ``dict``, etc.) for
1015-
concrete implementations. The return values of protocols
1016-
and abstract base classes must be judged on a case-by-case basis.
1017-
1018-
Yes::
1019-
1020-
def map_it(input: Iterable[str]) -> list[int]: ...
1021-
def create_map() -> dict[str, int]: ...
1022-
def to_string(o: object) -> str: ... # accepts any object
1023-
1024-
No::
1025-
1026-
def map_it(input: list[str]) -> list[int]: ...
1027-
def create_map() -> MutableMapping[str, int]: ...
1028-
def to_string(o: Any) -> str: ...
1029-
1030-
Maybe::
1031-
1032-
class MyProto(Protocol):
1033-
def foo(self) -> list[int]: ...
1034-
def bar(self) -> Mapping[str]: ...
1035-
1036-
Avoid union return types, since they require ``isinstance()`` checks.
1037-
Use ``Any`` or ``X | Any`` if necessary.
1038-
1039-
Use built-in generics instead of the aliases from ``typing``,
1040-
where possible. See the section `Built-in Generics`_ for cases,
1041-
where it's not possible to use them.
1042-
1043-
Yes::
1044-
1045-
from collections.abc import Iterable
1046-
1047-
def foo(x: type[MyClass]) -> list[str]: ...
1048-
def bar(x: Iterable[str]) -> None: ...
1049-
1050-
No::
1051-
1052-
from typing import Iterable, List, Type
1053-
1054-
def foo(x: Type[MyClass]) -> List[str]: ...
1055-
def bar(x: Iterable[str]) -> None: ...
1056-
1057966
NamedTuple and TypedDict
1058967
------------------------
1059968

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

1063972
Yes::
1064973

0 commit comments

Comments
 (0)