diff options
Diffstat (limited to 'docs/source/stubs.rst')
-rw-r--r-- | docs/source/stubs.rst | 1129 |
1 files changed, 1129 insertions, 0 deletions
diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst new file mode 100644 index 0000000..ffd6c9b --- /dev/null +++ b/docs/source/stubs.rst @@ -0,0 +1,1129 @@ +.. _stubs: + +********** +Type Stubs +********** + +Introduction +============ + +*type stubs*, also called *stub files*, provide type information for untyped +Python packages and modules. Type stubs serve multiple purposes: + +* They are the only way to add type information to extension modules. +* They can provide type information for packages that do not wish to + add them inline. +* They can be distributed separately from the implementation. + This allows stubs to be developed at a different pace or by different + authors, which is especially useful when adding type annotations to + existing packages. +* They can act as documentation, succinctly explaining the external + API of a package, without including the implementation or private + members. + +This document aims to give guidance to both authors of type stubs and developers +of type checkers and other tools. It describes the constructs that can be used safely in type stubs, +suggests a style guide for them, and lists constructs that type +checkers are expected to support. + +Type stubs that only use constructs described in this document should work with +all type checkers that also follow this document. +Type stub authors can elect to use additional constructs, but +must be prepared that some type checkers will not parse them as expected. + +A type checker that conforms to this document will parse a type stub that only uses +constructs described here without error and will not interpret any +construct in a contradictory manner. However, type checkers are not +required to implement checks for all these constructs, and +can elect to ignore unsupported ones. Additionally type checkers +can support constructs not described in this document and tool authors are +encouraged to experiment with additional features. + +Syntax +====== + +Type stubs are syntactically valid Python 3.7 files with a ``.pyi`` suffix. +The Python syntax used for type stubs is independent from the Python +versions supported by the implementation, and from the Python version the type +checker runs under (if any). Therefore, type stub authors should use the +latest available syntax features in stubs (up to Python 3.7), even if the +implementation supports older, pre-3.7 Python versions. +Type checker authors are encouraged to support syntax features from +post-3.7 Python versions, although type stub authors should not use such +features if they wish to maintain compatibility with all type checkers. + +For example, Python 3.7 added the ``async`` keyword (see PEP 492 [#pep492]_). +Stub authors should use it to mark coroutines, even if the implementation +still uses the ``@coroutine`` decorator. On the other hand, type stubs should +not use the positional-only syntax from PEP 570 [#pep570]_, introduced in +Python 3.8, although type checker authors are encouraged to support it. + +Stubs are treated as if ``from __future__ import annotations`` is enabled. +In particular, built-in generics, pipe union syntax (``X | Y``), and forward +references can be used. + +Starting with Python 3.8, the :py:mod:`ast` module from the standard library supports +all syntax features required by this PEP. Older Python versions can use the +`typed_ast <https://pypi.org/project/typed-ast/>`_ package from the +Python Package Index, which also supports Python 3.7 syntax and ``# type`` +comments. + +Distribution +============ + +Type stubs can be distributed with or separately from the implementation; +see PEP 561 [#pep561]_ for more information. The +`typeshed <https://github.com/python/typeshed>`_ project +includes stubs for Python's standard library and several third-party +packages. The stubs for the standard library are usually distributed with type checkers and do not +require separate installation. Stubs for third-party libraries are +available on the `Python Package Index <https://pypi.org>`_. A stub package for +a library called ``widget`` will be called ``types-widget``. + +Supported Constructs +==================== + +This sections lists constructs that type checkers will accept in type stubs. +Type stub authors can safely use these constructs. If a +construct is marked as "unspecified", type checkers may handle it +as they best see fit or report an error. Linters should usually +flag those constructs. Type stub authors should avoid using them to +ensure compatibility across type checkers. + +Unless otherwise mentioned, type stubs support all features from the +``typing`` module of the latest released Python version. If a stub uses +typing features from a later Python version than what the implementation +supports, these features can be imported from ``typing_extensions`` instead +of ``typing``. + +For example, a stub could use ``Literal``, introduced in Python 3.8, +for a library supporting Python 3.7+:: + + from typing_extensions import Literal + + def foo(x: Literal[""]) -> int: ... + +Comments +-------- + +Standard Python comments are accepted everywhere Python syntax allows them. + +Two kinds of structured comments are accepted: + +* A ``# type: X`` comment at the end of a line that defines a variable, + declaring that the variable has type ``X``. However, PEP 526-style [#pep526]_ + variable annotations are preferred over type comments. +* A ``# type: ignore`` comment at the end of any line, which suppresses all type + errors in that line. The type checker mypy supports suppressing certain + type errors by using ``# type: ignore[error-type]``. This is not supported + by other type checkers and should not be used in stubs. + +Imports +------- + +Type stubs distinguish between imports that are re-exported and those +that are only used internally. Imports are re-exported if they use one of these +forms:[#pep484]_ + +* ``import X as X`` +* ``from Y import X as X`` +* ``from Y import *`` + +Here are some examples of imports that make names available for internal use in +a stub but do not re-export them:: + + import X + from Y import X + from Y import X as OtherX + +Type aliases can be used to re-export an import under a different name:: + + from foo import bar as _bar + new_bar = _bar # "bar" gets re-exported with the name "new_bar" + +Sub-modules are always exported when they are imported in a module. +For example, consider the following file structure:: + + foo/ + __init__.pyi + bar.pyi + +Then ``foo`` will export ``bar`` when one of the following constructs is used in +``__init__.pyi``:: + + from . import bar + from .bar import Bar + +Stubs support customizing star import semantics by defining a module-level +variable called ``__all__``. In stubs, this must be a string list literal. +Other types are not supported. Neither is the dynamic creation of this +variable (for example by concatenation). + +By default, ``from foo import *`` imports all names in ``foo`` that +do not begin with an underscore. When ``__all__`` is defined, only those names +specified in ``__all__`` are imported:: + + __all__ = ['public_attr', '_private_looking_public_attr'] + + public_attr: int + _private_looking_public_attr: int + private_attr: int + +Type checkers support cyclic imports in stub files. + +Built-in Generics +----------------- + +PEP 585 [#pep585]_ built-in generics are supported and should be used instead +of the corresponding types from ``typing``:: + + from collections import defaultdict + + def foo(t: type[MyClass]) -> list[int]: ... + x: defaultdict[int] + +Using imports from ``collections.abc`` instead of ``typing`` is +generally possible and recommended:: + + from collections.abc import Iterable + + def foo(iter: Iterable[int]) -> None: ... + +Unions +------ + +Declaring unions with ``Union`` and ``Optional`` is supported by all +type checkers. With a few exceptions [#ts-4819]_, the shorthand syntax +is also supported:: + + def foo(x: int | str) -> int | None: ... # recommended + def foo(x: Union[int, str]) -> Optional[int]: ... # ok + +Module Level Attributes +----------------------- + +Module level variables and constants can be annotated using either +type comments or variable annotation syntax:: + + x: int # recommended + x: int = 0 + x = 0 # type: int + x = ... # type: int + +The type of a variable is unspecified when the variable is unannotated or +when the annotation +and the assigned value disagree. As an exception, the ellipsis literal can +stand in for any type:: + + x = 0 # type is unspecified + x = ... # type is unspecified + x: int = "" # type is unspecified + x: int = ... # type is int + +Classes +------- + +Class definition syntax follows general Python syntax, but type checkers +are only expected to understand the following constructs in class bodies: + +* The ellipsis literal ``...`` is ignored and used for empty + class bodies. Using ``pass`` in class bodies is undefined. +* Instance attributes follow the same rules as module level attributes + (see above). +* Method definitions (see below) and properties. +* Method aliases. +* Inner class definitions. + +More complex statements don't need to be supported:: + + class Simple: ... + + class Complex(Base): + read_write: int + @property + def read_only(self) -> int: ... + def do_stuff(self, y: str) -> None: ... + doStuff = do_stuff + +The type of generic classes can be narrowed by annotating the ``self`` +argument of the ``__init__`` method:: + + class Foo(Generic[_T]): + @overload + def __init__(self: Foo[str], type: Literal["s"]) -> None: ... + @overload + def __init__(self: Foo[int], type: Literal["i"]) -> None: ... + @overload + def __init__(self, type: str) -> None: ... + +The class must match the class in which it is declared. Using other classes, +including sub or super classes, will not work. In addition, the ``self`` +annotation cannot contain type variables. + +.. _supported-functions: + +Functions and Methods +--------------------- + +Function and method definition syntax follows general Python syntax. +Unless an argument name is prefixed with two underscores (but not suffixed +with two underscores), it can be used as a keyword argument [#pep484]_:: + + # x is positional-only + # y can be used positionally or as keyword argument + # z is keyword-only + def foo(__x, y, *, z): ... + +PEP 570 [#pep570]_ style positional-only parameters are currently not +supported. + +If an argument or return type is unannotated, per PEP 484 [#pep484]_ its +type is assumed to be ``Any``. It is preferred to leave unknown +types unannotated rather than explicitly marking them as ``Any``, as some +type checkers can optionally warn about unannotated arguments. + +If an argument has a literal or constant default value, it must match the implementation +and the type of the argument (if specified) must match the default value. +Alternatively, ``...`` can be used in place of any default value:: + + # The following arguments all have type Any. + def unannotated(a, b=42, c=...): ... + # The following arguments all have type int. + def annotated(a: int, b: int = 42, c: int = ...): ... + # The following default values are invalid and the types are unspecified. + def invalid(a: int = "", b: Foo = Foo()): ... + +For a class ``C``, the type of the first argument to a classmethod is +assumed to be ``type[C]``, if unannotated. For other non-static methods, +its type is assumed to be ``C``:: + + class Foo: + def do_things(self): ... # self has type Foo + @classmethod + def create_it(cls): ... # cls has type Type[Foo] + @staticmethod + def utility(x): ... # x has type Any + +But:: + + _T = TypeVar("_T") + + class Foo: + def do_things(self: _T) -> _T: ... # self has type _T + @classmethod + def create_it(cls: _T) -> _T: ... # cls has type _T + +PEP 612 [#pep612]_ parameter specification variables (``ParamSpec``) +are supported in argument and return types:: + + _P = ParamSpec("_P") + _R = TypeVar("_R") + + def foo(cb: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> _R: ... + +However, ``Concatenate`` from PEP 612 is not yet supported; nor is using +a ``ParamSpec`` to parameterize a generic class. + +PEP 647 [#pep647]_ type guards are supported. + +Using a function or method body other than the ellipsis literal is currently +unspecified. Stub authors may experiment with other bodies, but it is up to +individual type checkers how to interpret them:: + + def foo(): ... # compatible + def bar(): pass # behavior undefined + +All variants of overloaded functions and methods must have an ``@overload`` +decorator:: + + @overload + def foo(x: str) -> str: ... + @overload + def foo(x: float) -> int: ... + +The following (which would be used in the implementation) is wrong in +type stubs:: + + @overload + def foo(x: str) -> str: ... + @overload + def foo(x: float) -> int: ... + def foo(x: str | float) -> Any: ... + +Aliases and NewType +------------------- + +Type checkers should accept module-level type aliases, optionally using +``TypeAlias`` (PEP 613 [#pep613]_), e.g.:: + + _IntList = list[int] + _StrList: TypeAlias = list[str] + +Type checkers should also accept regular module-level or class-level aliases, +e.g.:: + + def a() -> None: ... + b = a + + class C: + def f(self) -> int: ... + g = f + +A type alias may contain type variables. As per PEP 484 [#pep484]_, +all type variables must be substituted when the alias is used:: + + _K = TypeVar("_K") + _V = TypeVar("_V") + _MyMap: TypeAlias = dict[str, dict[_K, _V]] + + # either concrete types or other type variables can be substituted + def f(x: _MyMap[str, _V]) -> _V: ... + # explicitly substitute in Any rather than using a bare alias + def g(x: _MyMap[Any, Any]) -> Any: ... + +Otherwise, type variables in aliases follow the same rules as type variables in +generic class definitions. + +``typing.NewType`` is also supported in stubs. + +Decorators +---------- + +Type stubs may only use decorators defined in the ``typing`` module, plus a +fixed set of additional ones: + +* ``classmethod`` +* ``staticmethod`` +* ``property`` (including ``.setter``) +* ``abc.abstractmethod`` +* ``dataclasses.dataclass`` +* ``asyncio.coroutine`` (although ``async`` should be used instead) + +The behavior of other decorators should instead be incorporated into the types. +For example, for the following function:: + + import contextlib + @contextlib.contextmanager + def f(): + yield 42 + +the stub definition should be:: + + from contextlib import AbstractContextManager + def f() -> AbstractContextManager[int]: ... + +Version and Platform Checks +--------------------------- + +Type stubs for libraries that support multiple Python versions can use version +checks to supply version-specific type hints. Type stubs for different Python +versions should still conform to the most recent supported Python version's +syntax, as explain in the Syntax_ section above. + +Version checks are if-statements that use ``sys.version_info`` to determine the +current Python version. Version checks should only check against the ``major`` and +``minor`` parts of ``sys.version_info``. Type checkers are only required to +support the tuple-based version check syntax:: + + if sys.version_info >= (3,): + # Python 3-specific type hints. This tuple-based syntax is recommended. + else: + # Python 2-specific type hints. + + if sys.version_info >= (3, 5): + # Specific minor version features can be easily checked with tuples. + + if sys.version_info < (3,): + # This is only necessary when a feature has no Python 3 equivalent. + +Type stubs should avoid checking against ``sys.version_info.major`` +directly and should not use comparison operators other than ``<`` and ``>=``. + +No:: + + if sys.version_info.major >= 3: + # Semantically the same as the first tuple check. + + if sys.version_info[0] >= 3: + # This is also the same. + + if sys.version_info <= (2, 7): + # This does not work because e.g. (2, 7, 1) > (2, 7). + +Some type stubs also may need to specify type hints for different platforms. +Platform checks must be equality comparisons between ``sys.platform`` and the name +of a platform as a string literal: + +Yes:: + + if sys.platform == 'win32': + # Windows-specific type hints. + else: + # Posix-specific type hints. + +No:: + + if sys.platform.startswith('linux'): + # Not necessary since Python 3.3. + + if sys.platform in ['linux', 'cygwin', 'darwin']: + # Only '==' or '!=' should be used in platform checks. + +Version and platform comparisons can be chained using the ``and`` and ``or`` +operators:: + + if sys.platform == 'linux' and (sys.version_info < (3,) or sys,version_info >= (3, 7)): ... + +Enums +----- + +Enum classes are supported in stubs, regardless of the Python version targeted by +the stubs. + +Enum members may be specified just like other forms of assignments, for example as +``x: int``, ``x = 0``, or ``x = ...``. The first syntax is preferred because it +allows type checkers to correctly type the ``.value`` attribute of enum members, +without providing unnecessary information like the runtime value of the enum member. + +Additional properties on enum members should be specified with ``@property``, so they +do not get interpreted by type checkers as enum members. + +Yes:: + + from enum import Enum + + class Color(Enum): + RED: int + BLUE: int + @property + def rgb_value(self) -> int: ... + + class Color(Enum): + # discouraged; type checkers will not understand that Color.RED.value is an int + RED = ... + BLUE = ... + @property + def rgb_value(self) -> int: ... + +No:: + + from enum import Enum + + class Color(Enum): + RED: int + BLUE: int + rgb_value: int # no way for type checkers to know that this is not an enum member + +Unsupported Features +-------------------- + +Currently, the following features are not supported by all type checkers +and should not be used in stubs: + +* Positional-only argument syntax (PEP 570 [#pep570]_). Instead, use + the syntax described in the section :ref:`supported-functions`. + [#ts-4972]_ + +Type Stub Content +================= + +This section documents best practices on what elements to include or +leave out of type stubs. + +Modules excluded fom stubs +-------------------------- + +Not all modules should be included into stubs. + +It is recommended to exclude: + +1. Implementation details, with `multiprocessing/popen_spawn_win32.py <https://github.com/python/cpython/blob/main/Lib/multiprocessing/popen_spawn_win32.py>`_ as a notable example +2. Modules that are not supposed to be imported, such as ``__main__.py`` +3. Protected modules that start with a single ``_`` char. However, when needed protected modules can still be added (see :ref:`undocumented-objects` section below) + +Public Interface +---------------- + +Stubs should include the complete public interface (classes, functions, +constants, etc.) of the module they cover, but it is not always +clear exactly what is part of the interface. + +The following should always be included: + +* All objects listed in the module's documentation. +* All objects included in ``__all__`` (if present). + +Other objects may be included if they are not prefixed with an underscore +or if they are being used in practice. (See the next section.) + +.. _undocumented-objects: + +Undocumented Objects +-------------------- + +Undocumented objects may be included as long as they are marked with a comment +of the form ``# undocumented``. + +Example:: + + def list2cmdline(seq: Sequence[str]) -> str: ... # undocumented + +Such undocumented objects are allowed because omitting objects can confuse +users. Users who see an error like "module X has no attribute Y" will +not know whether the error appeared because their code had a bug or +because the stub is wrong. Although it may also be helpful for a type +checker to point out usage of private objects, false negatives (no errors for +wrong code) are preferable over false positives (type errors +for correct code). In addition, even for private objects a type checker +can be helpful in pointing out that an incorrect type was used. + +``__all__`` +------------ + +A type stub should contain an ``__all__`` variable if and only if it also +present at runtime. In that case, the contents of ``__all__`` should be +identical in the stub and at runtime. If the runtime dynamically adds +or removes elements (for example if certain functions are only available on +some platforms), include all possible elements in the stubs. + +Stub-Only Objects +----------------- + +Definitions that do not exist at runtime may be included in stubs to aid in +expressing types. Sometimes, it is desirable to make a stub-only class available +to a stub's users - for example, to allow them to type the return value of a +public method for which a library does not provided a usable runtime type:: + + from typing import Protocol + + class _Readable(Protocol): + def read(self) -> str: ... + + def get_reader() -> _Readable: ... + +Structural Types +---------------- + +As seen in the example with ``_Readable`` in the previous section, a common use +of stub-only objects is to model types that are best described by their +structure. These objects are called protocols [#pep544]_, and it is encouraged +to use them freely to describe simple structural types. + +It is `recommended <#private-definitions>`_ to prefix stubs-only object names with ``_``. + +Incomplete Stubs +---------------- + +Partial stubs can be useful, especially for larger packages, but they should +follow the following guidelines: + +* Included functions and methods should list all arguments, but the arguments + can be left unannotated. +* Do not use ``Any`` to mark unannotated arguments or return values. +* Partial classes should include a ``__getattr__()`` method marked with an + ``# incomplete`` comment (see example below). +* Partial modules (i.e. modules that are missing some or all classes, + functions, or attributes) should include a top-level ``__getattr__()`` + function marked with an ``# incomplete`` comment (see example below). +* Partial packages (i.e. packages that are missing one or more sub-modules) + should have a ``__init__.pyi`` stub that is marked as incomplete (see above). + A better alternative is to create empty stubs for all sub-modules and + mark them as incomplete individually. + +Example of a partial module with a partial class ``Foo`` and a partially +annotated function ``bar()``:: + + def __getattr__(name: str) -> Any: ... # incomplete + + class Foo: + def __getattr__(self, name: str) -> Any: # incomplete + x: int + y: str + + def bar(x: str, y, *, z=...): ... + +The ``# incomplete`` comment is mainly intended as a reminder for stub +authors, but can be used by tools to flag such items. + +Attribute Access +---------------- + +Python has several methods for customizing attribute access: ``__getattr__``, +``__getattribute__``, ``__setattr__``, and ``__delattr__``. Of these, +``__getattr__`` and ``__setattr___`` should sometimes be included in stubs. + +In addition to marking incomplete definitions, ``__getattr__`` should be +included when a class or module allows any name to be accessed. For example, consider +the following class:: + + class Foo: + def __getattribute__(self, name): + return self.__dict__.setdefault(name) + +An appropriate stub definition is:: + + from typing import Any + class Foo: + def __getattr__(self, name: str) -> Any | None: ... + +Note that only ``__getattr__``, not ``__getattribute__``, is guaranteed to be +supported in stubs. + +On the other hand, ``__getattr__`` should be omitted even if the source code +includes it, if only limited names are allowed. For example, consider this class:: + + class ComplexNumber: + def __init__(self, n): + self._n = n + def __getattr__(self, name): + if name in ("real", "imag"): + return getattr(self._n, name) + raise AttributeError(name) + +In this case, the stub should list the attributes individually:: + + class ComplexNumber: + @property + def real(self) -> float: ... + @property + def imag(self) -> float: ... + def __init__(self, n: complex) -> None: ... + +``__setattr___`` should be included when a class allows any name to be set and +restricts the type. For example:: + + class IntHolder: + def __setattr__(self, name, value): + if isinstance(value, int): + return super().__setattr__(name, value) + raise ValueError(value) + +A good stub definition would be:: + + class IntHolder: + def __setattr__(self, name: str, value: int) -> None: ... + +``__delattr__`` should not be included in stubs. + +Finally, even in the presence of ``__getattr__`` and ``__setattr__``, it is +still recommended to separately define known attributes. + +Constants +--------- + +When the value of a constant is important, annotate it using ``Literal`` +instead of its type. + +Yes:: + + TEL_LANDLINE: Literal["landline"] + TEL_MOBILE: Literal["mobile"] + DAY_FLAG: Literal[0x01] + NIGHT_FLAG: Literal[0x02] + +No:: + + TEL_LANDLINE: str + TEL_MOBILE: str + DAY_FLAG: int + NIGHT_FLAG: int + +Documentation or Implementation +------------------------------- + +Sometimes a library's documented types will differ from the actual types in the +code. In such cases, type stub authors should use their best judgment. Consider +these two examples:: + + def print_elements(x): + """Print every element of list x.""" + for y in x: + print(y) + + def maybe_raise(x): + """Raise an error if x (a boolean) is true.""" + if x: + raise ValueError() + +The implementation of ``print_elements`` takes any iterable, despite the +documented type of ``list``. In this case, annotate the argument as +``Iterable[Any]``, to follow this PEP's style recommendation of preferring +abstract types. + +For ``maybe_raise``, on the other hand, it is better to annotate the argument as +``bool`` even though the implementation accepts any object. This guards against +common mistakes like unintentionally passing in ``None``. + +If in doubt, consider asking the library maintainers about their intent. + +Style Guide +=========== + +The recommendations in this section are aimed at type stub authors +who wish to provide a consistent style for type stubs. Type checkers +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 +different structure of stub files into account and are aimed to create +more concise files. + +Maximum Line Length +------------------- + +Type stubs should be limited to 130 characters per line. + +Blank Lines +----------- + +Do not use empty lines between functions, methods, and fields, except to +group them with one empty line. Use one empty line around classes, but do not +use empty lines between body-less classes, except for grouping. + +Yes:: + + def time_func() -> None: ... + def date_func() -> None: ... + + def ip_func() -> None: ... + + class Foo: + x: int + y: int + def __init__(self) -> None: ... + + class MyError(Exception): ... + class AnotherError(Exception): ... + +No:: + + def time_func() -> None: ... + + def date_func() -> None: ... # do no leave unnecessary empty lines + + def ip_func() -> None: ... + + + class Foo: # leave only one empty line above + 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 +----------------------- + +Do not use an assignment for module-level attributes. + +Yes:: + + CONST: Literal["const"] + x: int + +No:: + + CONST = "const" + x: int = 0 + y: float = ... + 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 + +Classes +------- + +Classes without bodies should use the ellipsis literal ``...`` in place +of the body on the same line as the class definition. + +Yes:: + + class MyError(Exception): ... + +No:: + + class MyError(Exception): + ... + class AnotherError(Exception): pass + +Instance attributes and class variables follow the same recommendations as +module level attributes: + +Yes:: + + class Foo: + c: ClassVar[str] + x: int + +No:: + + class Foo: + c: ClassVar[str] = "" + d: ClassVar[int] = ... + x = 4 + y: int = ... + +Functions and Methods +--------------------- + +Use the same argument names as in the implementation, because +otherwise using keyword arguments will fail. Of course, this +does not apply to positional-only arguments, which are marked with a double +underscore. + +Use the ellipsis literal ``...`` in place of actual default argument +values. Use an explicit ``X | None`` annotation instead of +a ``None`` default. + +Yes:: + + def foo(x: int = ...) -> None: ... + def bar(y: str | None = ...) -> None: ... + +No:: + + def foo(x: int = 0) -> None: ... + def bar(y: str = None) -> None: ... + def baz(z: str | None = None) -> None: ... + +Do not annotate ``self`` and ``cls`` in method definitions, except when +referencing a type variable. + +Yes:: + + _T = TypeVar("_T") + class Foo: + def bar(self) -> None: ... + @classmethod + def create(cls: type[_T]) -> _T: ... + +No:: + + class Foo: + def bar(self: Foo) -> None: ... + @classmethod + def baz(cls: type[Foo]) -> int: ... + +The bodies of functions and methods should consist of only the ellipsis +literal ``...`` on the same line as the closing parenthesis and colon. + +Yes:: + + def to_int1(x: str) -> int: ... + def to_int2( + x: str, + ) -> int: ... + +No:: + + def to_int1(x: str) -> int: + return int(x) + def to_int2(x: str) -> int: + ... + def to_int3(x: str) -> int: pass + +.. _private-definitions: + +Private Definitions +------------------- + +Type variables, type aliases, and other definitions that should not +be used outside the stub should be marked as private by prefixing them +with an underscore. + +Yes:: + + _T = TypeVar("_T") + _DictList = Dict[str, List[Optional[int]] + +No:: + + T = TypeVar("T") + DictList = Dict[str, List[Optional[int]]] + +Language Features +----------------- + +Use the latest language features available as outlined +in the Syntax_ section, even for stubs targeting older Python versions. +Do not use quotes around forward references and do not use ``__future__`` +imports. + +Yes:: + + class Py35Class: + x: int + forward_reference: OtherClass + class OtherClass: ... + +No:: + + class Py35Class: + x = 0 # type: int + 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 argument types, +use ``bytes`` instead of ``bytes | memoryview | bytearray``. + +Use ``Text`` in stubs that support Python 2 when something accepts both +``str`` and ``unicode``. Avoid using ``Text`` in stubs or branches for +Python 3 only. + +Yes:: + + if sys.version_info < (3,): + def foo(s: Text) -> None: ... + else: + def foo(s: str, *, i: int) -> None: ... + def bar(s: Text) -> None: ... + +No:: + + if sys.version_info < (3,): + def foo(s: unicode) -> None: ... + else: + def foo(s: Text, *, i: int) -> 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. + +Yes:: + + from typing import NamedTuple, TypedDict + class Point(NamedTuple): + x: float + y: float + + class Thing(TypedDict): + stuff: str + index: int + +No:: + + from typing import NamedTuple, TypedDict + Point = NamedTuple("Point", [('x', float), ('y', float)]) + Thing = TypedDict("Thing", {'stuff': str, 'index': int}) + +References +========== + +PEPs +---- + +.. [#pep8] PEP 8 -- Style Guide for Python Code, van Rossum et al. (https://www.python.org/dev/peps/pep-0008/) +.. [#pep484] PEP 484 -- Type Hints, van Rossum et al. (https://www.python.org/dev/peps/pep-0484) +.. [#pep492] PEP 492 -- Coroutines with async and await syntax, Selivanov (https://www.python.org/dev/peps/pep-0492/) +.. [#pep526] PEP 526 -- Syntax for Variable Annotations, Gonzalez et al. (https://www.python.org/dev/peps/pep-0526) +.. [#pep544] PEP 544 -- Protocols: Structural Subtyping, Levkivskyi et al. (https://www.python.org/dev/peps/pep-0544) +.. [#pep561] PEP 561 -- Distributing and Packaging Type Information, Smith (https://www.python.org/dev/peps/pep-0561) +.. [#pep570] PEP 570 -- Python Positional-Only Parameters, Hastings et al. (https://www.python.org/dev/peps/pep-0570) +.. [#pep585] PEP 585 -- Type Hinting Generics In Standard Collections, Langa (https://www.python.org/dev/peps/pep-0585) +.. [#pep604] PEP 604 -- Allow writing union types as X | Y, Prados and Moss (https://www.python.org/dev/peps/pep-0604) +.. [#pep612] PEP 612 -- Parameter Specification Variables, Mendoza (https://www.python.org/dev/peps/pep-0612) +.. [#pep613] PEP 613 -- Explicit Type Aliases, Zhu (https://www.python.org/dev/peps/pep-0613) +.. [#pep647] PEP 647 -- User-Defined Type Guards, Traut (https://www.python.org/dev/peps/pep-0647) +.. [#pep3107] PEP 3107 -- Function Annotations, Winter and Lownds (https://www.python.org/dev/peps/pep-3107) + +Bugs +---- + +.. [#ts-4819] typeshed issue #4819 -- PEP 604 tracker (https://github.com/python/typeshed/issues/4819) +.. [#ts-4972] typeshed issue #4972 -- PEP 570 tracker (https://github.com/python/typeshed/issues/4972) + +Copyright +========= + +This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive. |