Programming in Python

Documentation

Gerald Senarclens de Grancy

What can be said
in code, should be!

Documentation

Source code represents primary knowledge

Secondary knowledge takes the form of
more human-readable documentation

Every package/ module/ class/ function should have a
one-liner stating what its purpose is

Given the source code - is there a need for secondary documentation?

  • Yes!
  • Representing a model/ thoughts in code is easier than deducting that model/ these thoughts from code
  • Document the high level
  • Document reasoning behind decisions

What should be documented?

  • Everything that should be explained, but can't be said in code
  • High level documentation
  • Reasoning behind (design) decisions
  • Installation and usage instructions
  • ...

Magic Constants / Numbers

Values with unexplained meaning which
should be replaced with a named constant

Bad for code maintenance and readability

# ...
profit = round(profit, 2)
effort = round(effort, 2)
postal_code = data.get("postal_code", 8010)
PRECISION = 2  # use given precision throughout the program
DEFAULT_POSTAL_CODE = 8010
POSTAL_CODE_KEY = "postal_code"
# ...
profit = round(profit, PRECISION)
effort = round(effort, PRECISION)
postal_code = data.get(POSTAL_CODE_KEY, DEFAULT_POSTAL_CODE)

Docstrings

  • Use triple-quoted strings: """ or '''
  • From within Python, __doc__ gives access to an object's docstring
  • To access the documentation, you can use the help function
"""
.. moduleauthor:: Gerald Senarclens de Grancy <oss@senarclens.eu>

This module provides basic mathematical functions.
"""

def add(first, second):
    """
    Return the sum of the parameters.
    """
    return first + second

print(__doc__)  # access the module documentation
print(add.__doc__)  # access the documentation for `add`
help(add)  # access interactive help in the interpreter; `add?` in ipython
Download doc.py

Comments vs. Docstrings

  • Comments stay inside the code
  • Documentation strings (docstrings) are used to generate
    designated documentation
  • It is too easy to forget updating external documentation

Use docstrings if available in your language

Python has excellent support for docstrings!

You may use docstings to give hints on data types

def add(first, second):
    """
    Return the sum of the parameters as integer or floating point number.

    Arguments:
    first -- an integer or floating point number
    second -- an integer or floating point number
    """
    return first + second
Download docstring_type_hints.py

However, it is much better to use type annotations for this purpose

def add(first: float, second: float) -> float:
    """
    Return the sum of the parameters.
    """
    return first + second
Download function_annotation.py

Arguments annotated as float also accept int ( The numeric tower)

Annotations

annotations
Labels associated with variables, class attributes or functions
Used by convention as a type hint
function annotation
An annotation of a function parameter or return value
PEP 484
variable annotation
An annotation of a variable or a class attribute
PEP 526

Annotations - Example

from typing import TypeAlias
Iterable: TypeAlias = list | tuple  # variable annotation as `TypeAlias`

def demo(first: list, second: Iterable) -> int:
    """
    Demonstrate type hints.
    """
    first.extend(second)
    count: int = len(first)  # variable annotation as `int`
    return count
Download type_annotation.py

Annotations - Type Checkers

There are multiple third party tools that allow type checking

mypy (repository)
optional static type checker for Python (root: Dropbox)
Pyre + Pysa (repository)
type-checker and static (security) analysis (Facebook)
Pyright (repository)
static type checker for Python (Microsoft)
pytype (repository)
checks and infers types for your Python code - without requiring type annotations (Google)

Annotations - mypy

Download type_issue.py

sudo apt install python3-mypy (or using a venv)

python type_issue.py

Look at the code and try to figure out what's wrong

Or ... ask mypy to work for you

mypy type_issue.py

Doctests

Key purpose is tutorial documentation

  • Provide examples of what functions/ classes/ modules/ packages do
  • Show how they are to be used

Dangers

  • Doctests should be an addition to unit tests - not a replacement
  • Avoid cluttering your code

Syntax

  • Derived from the standard Python interpreter shell
  • Primary prompt: >>> followed by new commands
  • Secondary prompt: ... when continuing commands on multiple lines
  • Expected results are displayed without indentation
  • Blank line and lines starting with >>> mark the end of the output
To write tests
use the python3 interpreter or
set %doctest_mode in ipython

Example

def add(first: float, second: float) -> float:
    """
    Return the sum of the parameters.

    >>> add(1,3)
    4
    >>> add(7.3, -3.0)
    4.3
    >>> add(42, "foo")
    Traceback (most recent call last):
        ...
    TypeError: unsupported operand type(s) for +: 'int' and 'str'
    """
    return first + second
Download doctest_sample.py

Doctests can also be written in textual documentation files

The ``doctest_sample`` module
=============================
Using ``add``
-------------

This is an example text file in reStructuredText format.  First import
``add`` from the ``doctest_sample`` module:

    >>> from doctest_sample import add

Now use it:

    >>> add(6, 5)
    120

Documentation like this can be used by the Sphinx documentation generator.
Download doctest_sample.rst

Execution

No output is shown when tests pass (unless -v is used)

$ python3 -m doctest doctest_sample.py  # no output for passing tests
$ python3 -m doctest -v doctest_sample.py  # detailed output
$ python3 -m doctest -v doctest_sample.rst  # run against textual documentation

Running executable scripts with -m doctest doesn't work (-m doctest would be an argument to the script and not to the Python interpreter)

Doctests can also be exectured from within Python scripts, e.g.

if __name__=="__main__":  # not executed when imported as a module
    import doctest
    doctest.testmod()  # or doctest.testmod(verbose=True)

If the code and the comments disagree,
then both are probably wrong.

Norm Schryer

Summary

  • What can be said in code, should be
  • Document everything you deem appropriate
  • Use doctests when appropriate, but don't clutter your source

Questions
and feedback...

Further Reading

David Goodger and Guido van Rossum PEP 257 - Docstring Conventions http://www.python.org/dev/peps/pep-0257/
Kevlin Henney (edt.) 97 Things Every Programmer Should Know O'Reilly (2010)
Mark Pilgrim Dive Into Python 3 (2nd edition) Apress (October 23, 2009)
Python Software Foundation Python Documentation http://docs.python.org/3/