7

Is there a best practice for using type aliases or typing objects in docstrings?

This question might attract opinion based answers. But it could also be that there is a widely accepted convention or external tool support for a particular solution.

Related question


Example: a function returns a dictionary with string keys and values. What type would you put into the docstring under the "Returns" section? (I am using pandas style docstrings.)

Option 1: just say it's a dict.

import typing

strstrdict = typing.Dict[str, str]

def foo() -> strstrdict:
    '''
    bla bla

    Returns
    -------
    dict
        A dictionary with string keys and values that represents ...
    '''
    # code

Option 2: use a type alias.

import typing

strstrdict = typing.Dict[str, str]

def foo() -> strstrdict:
    '''
    bla bla

    Returns
    -------
    strstrdict
        A dictionary with string keys and values that represents ...
    '''
    # code

Option 3: put "typing.Dict[str, str]" into the docstring.

import typing

strstrdict = typing.Dict[str, str]

def foo() -> strstrdict:
    '''
    bla bla

    Returns
    -------
    typing.Dict[str, str]
        A dictionary with string keys and values that represents ...
    '''
    # code

Option 4: something else?

EDIT 1

"I am using pandas style docstrings" Are you looking for answer for just this style or in general?

I guess the optimal answer would cover the general and specific case as much as possible. I mentioned the pandas style to make clear why there is a "Returns" section and no instructions like ":param:". I'm not dead set on the style with respect to an answer.

Do you actually include the aliases in your documentation, i.e. can users discover what the alias strstrdict is?

Currently there's no documentation for the alias. The user could view themodule.strstrdict. I'm open to suggestions here.

EDIT 2

The style guide I linked to coincidentally mentions a dict with string keys and values. The answer I am looking for should also cover cases like this:

from typing import Any, Callable, ContextManager, Iterable

ContextCallables = ContextManager[Iterable[Callable[[int, int], Any]]]

def foo() -> ContextCallabes:
    '''
    bla bla

    Returns
    -------
    ???
           some description
    '''
    # code
actual_panda
  • 1,178
  • 9
  • 27
  • "I am using pandas style docstrings" Are you looking for answer for just this style or in general? Do you actually include the aliases in your documentation, i.e. can users discover what the alias ``strstrdict`` is? – MisterMiyagi Apr 03 '20 at 09:42
  • @MisterMiyagi Questions can only ask about a specific style, otherwise they would have to be closed as opinion-based. – a_guest Apr 03 '20 at 09:43
  • @a_guest Neither the [pandas docstring guide](https://pandas.pydata.org/docs/development/contributing_docstring.html) nor the [numpydoc docstring guide](https://numpydoc.readthedocs.io/en/latest/format.html) are authoritative with respect to typing information provided via annotations/``typing``/aliases (the topic is not covered at all). Restricting the question to these styles (I'm not saying that's bad) does not prevent it from being opinion-based (I'm not saying it is in the first place). – MisterMiyagi Apr 03 '20 at 09:51
  • @MisterMiyagi I edited my question in response to your comment. – actual_panda Apr 03 '20 at 10:04
  • 2
    I think the practice of including the input/return type in the docstring predates PEP 484 types and might need reconsideration. Now, it might actually be better to omit the types from the docstring entirely if you're using type hints: it's redundant with what's already in the signature. Similarly, I'd expect any modern documentation tool to know how to work with type hints/expose them in a clean manner. For example, sphinx has several plugins for this (e.g. [sphinx-autodoc-typehints](https://github.com/agronholm/sphinx-autodoc-typehints)). This is all just my opinion, of course. – Michael0x2a Apr 04 '20 at 16:54

1 Answers1

1

Since you explicitly mentioned the doc-style convention that you are following, there shouldn't be an issue with opinion-based answers.

We can check the pandas docstring guide section about parameter types:

For complex types, define the subtypes. For dict and tuple, as more than one type is present, we use the brackets to help read the type (curly brackets for dict and normal brackets for tuple).

This means you should document the Dict[str, str] as follows:

Returns
-------
dict of {str : str}
    Some explanation here ...

You can check the docs for more examples, including other types.

a_guest
  • 34,165
  • 12
  • 64
  • 118
  • 2
    This seems very specific to the example, which just happens to be covered by the styles short notation. What about types not covered by the short notation? Say a ``ContextManager[Iterable[Callable[[int, int], Any]]]``? – MisterMiyagi Apr 03 '20 at 09:56
  • 2
    @MisterMiyagi I think the style guide is still clear about that: *"If the type is defined in a Python module, the module must be specified."*. The details can be explained in the parameter text. So for your example this would be just `typing.ContextManager`. Now what if we stick that into a dict and make something like `Dict[str, ContextManager[...]]`? The style guide mentions: *"For complex types, define the subtypes."* It doesn't say "... and do this recursively for every type." So it requires just one level of detail: `dict of {str : typing.ContextManager}`. More details can go to the text. – a_guest Apr 03 '20 at 10:14
  • 1
    I don't see how the style guide is clear about that. The guide is only about concrete types, with special rules for literal containers and the two abstract array-like and iterable. There is no notion of abstract generic types or their aliases as expressed by typing. – MisterMiyagi Apr 03 '20 at 10:38
  • 2
    @MisterMiyagi The guide includes the following part: *"If the type is defined in a Python module, [...]"*. So, for any type to be included into the type definition you can check whether this condition is fulfilled or not. If you annotate your function as `ContextManager[...]` then, since `ContextManager` is defined in a module (`typing`), it should be defined as `typing.ContextManager`. If on the other hand you used an alias `my_alias = ContextManager[...]` then this is not defined in a(nother) module and so you can use just `my_alias`. With Sphinx ``:data:`my_alias``` can be used for linking. – a_guest Apr 03 '20 at 11:25
  • 1
    That's sidestepping the issue. The question isn't whether ``ContextManager[Iterable[Callable[[int, int], Any]]]`` should be used with or without the module prefix(es). The question is whether it should be used verbatim in its full ugly-ness (option 3), shortened to the meaningless ``ContextManager`` (option 1), or with a meaningful but non-generic ``ContextualCallables`` (option 2). For concrete types, which is all the style guide talks about in the general case, all options are the same. For ``typing`` with abstract, generic and aliased types, that is not the case. – MisterMiyagi Apr 03 '20 at 11:34