PEP 727: Documentation Metadata in Typing

This is my biggest concern as well. Functions where the arguments have type annotations can already be rather long, and Annotated on its own is rather verbose, so I’m generally glad it’s rare. By making it the recommended way of documenting arguments, this makes use of Annotated much more common, and furthermore, an argument docstring will itself be pretty long, making the problem worse.

Combined with the long-standing pressure we get from people wanting to be able to write “one-liners” in Python, we’re reaching a point where a typical Python function will consist of a huge declaration, combined with a highly compact body. And I can’t think of anything less readable than this.

Here’s an example from a recent function I wrote:

def download_pip(
    version: Annotated[str, doc("The version of pip to download")],
    hash: Annotated[str, doc("The SHA256 hash of the wheel file that will be doenloaded")],
    target: Annotated[Path, doc("The path where the downloaded wheel should be stored. Any directories in the path must already exist, and it is the caller's responsibility to use a valid wheel filename"]
):
    url = wheel_url("pip", version, "", "py3", "none", "any")
    with urlopen(url) as f:
        content = f.read()
    if hashlib.sha256(content).hexdigest() != hash:
        raise ValueError("Downloaded file has invalid hash")
    target.write_bytes(content)

Try quickly finding the parameter types from that - I deliberately marked the code as “text” to demonstrate the issue if you don’t have some sort of syntax highlighting support. Also, the restrictions on what you can pass as the target argument are pushed way off the screen to the right (at least on my display) meaning that the docstring for the parameter isn’t readable without scrolling the page. And have fun noticing the spelling mistake in one of the docstrings!

The version that you get if you run black on this is even worse:

def download_pip(
    version: Annotated[str, doc("The version of pip to download")],
    hash: Annotated[
        str, doc("The SHA256 hash of the wheel file that will be doenloaded")
    ],
    target: Annotated[
        Path,
        doc(
            "The path where the downloaded wheel should be stored. Any directories in the path must already exist, and it is the caller's responsibility to use a valid wheel filename"
        ),
    ],
):
    url = wheel_url("pip", version, "", "py3", "none", "any")
    with urlopen(url) as f:
        content = f.read()
    if hashlib.sha256(content).hexdigest() != hash:
        raise ValueError("Downloaded file has invalid hash")
    target.write_bytes(content)

The actual code is almost entirely lost, it’s barely more than half the number of lines of the function header…

And all of this is just with 3 arguments, all with basic types. Here’s a function from pip, which I picked at random so can be considered more typical of “real world” code:

    def send(
        self,
        request: PreparedRequest,
        stream: bool = False,
        timeout: Optional[Union[float, Tuple[float, float]]] = None,
        verify: Union[bool, str] = True,
        cert: Optional[Union[str, Tuple[str, str]]] = None,
        proxies: Optional[Mapping[str, str]] = None,
    ) -> Response:

Add docstrings to that, if you dare :slightly_frowning_face:

Of course, the counter-arguments are “this is optional, you don’t have to use it” and “it’s up to the user to ensure their code is readable”. But honestly, those responses are at best naïve, and at worst dismissive of a real issue. People do demand policies like “your arguments must have types” and “your code must have docstrings” because IDEs can give a much better user experience to the user of the code if that data is available. It’s important, therefore, to make sure that including such information is possible without making things worse for the developer of the code, so that we encourage good practices.

If this were being proposed as an interim solution that could be used until a more developer-friendly solution was devised, then I’d be less concerned. But I don’t think we’re even close here to a solution that I’d want to advocate as the “one obvious way” to document function parameters.

33 Likes