pydoclint

Notes for users

Table of Contents

1. Why is pydoclint so much faster than darglint

Based on the best understanding of the authors of pydoclint, here are some reasons (this may not be an exhaustive list):

2. Cases that pydoclint is not designed to handle

pydoclint uses a static syntax analyzer (Python’s official AST module) to analyze the incoming Python source code.

The static syntax analysis is very fast because it doesn’t execute or evaluate any code. For example, this piece of Python code is not runnable:

a = b

because b is not defined. But the static syntax analyzer does not “know” this: it doesn’t need to “know” this to analyze the syntatic structure of a = b.

As a result, pydoclint is not designed to handle cases where Pythonic naming conventions are broken, such as:

hello = classmethod

class MyClass:
    @hello
    def myClassMethod(cls):
        pass
class MyClass:
    def myMethod(hello, arg1):  # the 1st argument is `self` by convention
        pass

    @classmethod
    def myClassMethod(hey, arg2):  # the 1st argument is `cls` by convention
        pass
from typing import List as hello
from typing import Optional as world

def myFunc(arg1: hello[int], arg2: world[str]) -> None:
    """
    An example function.

    pydoclint expects consistency between signature type annotation (`hello[int]`)
    and docstring type annotation (`List[int]`).

    Parameters
    ----------
    arg1 : List[int]
        Arg 1
    arg2 : world[str]
        Arg 2
    """
    print(arg1, arg2)

The authors of pydoclint feel that this is a sensible design choice to achieve and maintain pydoclint’s speed.

3. Notes on writing type hints

As mentioned in Section 2 above, pydoclint uses static syntax analysis. As a result, it cannot really “know” that these type annotations are in fact equivalent:

Type annotation Equivalent version
Optional[str] str | None
Union[str, int] int | str
Tuple[str, int] tuple[str, int]

Additionally, pydoclint does not recognize some docstring conventions allowed in the docstring style guide, such as using “int, optional” for Optional[int].

Right now, the only way to make pydoclint stop reporting style violations is to make sure the docstring type annotations match the signature type annotations verbatim.

Again, the authors of pydoclint feel that this is a reasonable price to pay in order to achieve fast linting and reduce ambiguity.

4. Notes on writing Sphinx-style docstrings

The official Sphinx documentation does not explicitly state this, so it is unclear what header to use to specify the type of what a function yields.

Many people use rtype, but the authors of pydoclint find it difficult to differentiate the type of return value and the type of yield value.

Therefore, pydoclint expects the convention of ytype for yield types. This is actually common practice, as evident from a code search on GitHub: https://github.com/search?q=%3Aytype%3A+language%3APython&type=code&l=Python

5. Notes for Google-style users

By default, pydoclint checks return type and yield type consistencies, and it also requires argument types in the docstring. In other words, by pydoclint’s default, this is an acceptable Google-style docstring:

"""
This is a function.

Args:
    arg1 (int): Arg 1
    arg2 (float): Arg 2
    arg3 (Optional[Union[float, int, str]]): Arg 3

Returns:
    int: Result
"""

However, this may not be the convention of a lot of Google-style docstring writers.

But do not worry: here are some config options to tweak:

Here are all the configurable options of pydoclint, and here is how to configure pydoclint.

6. How to adopt pydoclint more easily in legacy projects

If you have large legacy projects, adoting a new linter may be daunting: you’ll see hundreds or even thousands of violations at first.

Fortunately, pydoclint offers a “baseline” feature, which ignores existing violations for now, and will only report new violations.

To use this feature, you only need to generate a “baseline violations” file (containing the hundreds or thousands of existing violations) once, and save it somewhere in your repo.

For more details, please check out this section.