From ad4cbc45f868f47031494694bdb7349119302f59 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 3 Dec 2024 17:13:26 +0100 Subject: [PATCH] Writing Stubs: Add "Overloads and Flags" section (#1894) --- docs/guides/writing_stubs.rst | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/guides/writing_stubs.rst b/docs/guides/writing_stubs.rst index 7475cce0..bde28632 100644 --- a/docs/guides/writing_stubs.rst +++ b/docs/guides/writing_stubs.rst @@ -405,6 +405,64 @@ common mistakes like unintentionally passing in ``None``. If in doubt, consider asking the library maintainers about their intent. +Common Patterns +=============== + +.. _stub-patterns: + +This section documents common patterns that are useful in stub files. + +Overloads and Flags +------------------- + +.. _overloads-and-flags: + +Sometimes a function or method has a flag argument that changes the return type +or other accepted argument types. For example, take the following function:: + + def open(name: str, mode: Literal["r", "w"] = "r") -> Reader | Writer: + ... + +We can express this case easily with two overloads:: + + @overload + def open(name: str, mode: Literal["r"] = "r") -> Reader: ... + @overload + def open(name: str, mode: Literal["w"]) -> Writer: ... + +The first overload is picked when the mode is ``"r"`` or not given, and the +second overload is picked when the mode is ``"w"``. But what if the first +argument is optional? + +:: + + def open(name: str | None = None, mode: Literal["r", "w"] = "r") -> Reader | Writer: + ... + +Ideally we would be able to use the following overloads:: + + @overload + def open(name: str | None = None, mode: Literal["r"] = "r") -> Reader: ... + @overload + def open(name: str | None = None, mode: Literal["w"]) -> Writer: ... + +And while the first overload is fine, the second is a syntax error in Python, +because non-default arguments cannot follow default arguments. To work around +this, we need an extra overload:: + + @overload + def open(name: str | None = None, mode: Literal["r"] = "r") -> Reader: ... + @overload + def open(name: str | None, mode: Literal["w"]) -> Writer: ... + @overload + def open(*, mode: Literal["w"]) -> Writer: ... + +As before, the first overload is picked when the mode is ``"r"`` or not given. +Otherwise, the second overload is used when ``open`` is called with an explicit +``name``, e.g. ``open("file.txt", "w")`` or ``open(None, "w")``. The third +overload is used when ``open`` is called without a name , e.g. +``open(mode="w")``. + Style Guide ===========