Documentation

Python has great in-built documentation that is available via the help function. For example

In [1]:
l = ["cat", "dog", "fish"]
In [2]:
help(l)
Help on list object:

class list(object)
 |  list() -> new empty list
 |  list(iterable) -> new list initialized from iterable's items
 |  
 |  Methods defined here:
 |  
 |  __add__(self, value, /)
 |      Return self+value.
 |  
 |  __contains__(self, key, /)
 |      Return key in self.
 |  
 |  __delitem__(self, key, /)
 |      Delete self[key].
 |  
 |  __eq__(self, value, /)
 |      Return self==value.
 |  
 |  __ge__(self, value, /)
 |      Return self>=value.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __getitem__(...)
 |      x.__getitem__(y) <==> x[y]
 |  
 |  __gt__(self, value, /)
 |      Return self>value.
 |  
 |  __iadd__(self, value, /)
 |      Implement self+=value.
 |  
 |  __imul__(self, value, /)
 |      Implement self*=value.
 |  
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __le__(self, value, /)
 |      Return self<=value.
 |  
 |  __len__(self, /)
 |      Return len(self).
 |  
 |  __lt__(self, value, /)
 |      Return self<value.
 |  
 |  __mul__(self, value, /)
 |      Return self*value.n
 |  
 |  __ne__(self, value, /)
 |      Return self!=value.
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __reversed__(...)
 |      L.__reversed__() -- return a reverse iterator over the list
 |  
 |  __rmul__(self, value, /)
 |      Return self*value.
 |  
 |  __setitem__(self, key, value, /)
 |      Set self[key] to value.
 |  
 |  __sizeof__(...)
 |      L.__sizeof__() -- size of L in memory, in bytes
 |  
 |  append(...)
 |      L.append(object) -> None -- append object to end
 |  
 |  clear(...)
 |      L.clear() -> None -- remove all items from L
 |  
 |  copy(...)
 |      L.copy() -> list -- a shallow copy of L
 |  
 |  count(...)
 |      L.count(value) -> integer -- return number of occurrences of value
 |  
 |  extend(...)
 |      L.extend(iterable) -> None -- extend list by appending elements from the iterable
 |  
 |  index(...)
 |      L.index(value, [start, [stop]]) -> integer -- return first index of value.
 |      Raises ValueError if the value is not present.
 |  
 |  insert(...)
 |      L.insert(index, object) -- insert object before index
 |  
 |  pop(...)
 |      L.pop([index]) -> item -- remove and return item at index (default last).
 |      Raises IndexError if list is empty or index is out of range.
 |  
 |  remove(...)
 |      L.remove(value) -> None -- remove first occurrence of value.
 |      Raises ValueError if the value is not present.
 |  
 |  reverse(...)
 |      L.reverse() -- reverse *IN PLACE*
 |  
 |  sort(...)
 |      L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __hash__ = None

You can add similar documentation to the functions that you write. You do this by adding in a documentation string as the first string after defining the function e.g.

In [3]:
def multiply(a, b):
    """This function returns the element-wise multiplication of the passed lists 'a' and 'b'"""
    c = []
    for x,y in zip(a,b):
        c.append(x*y)
    return c
In [4]:
multiply( [1,2,3], [4,5,6] )
Out[4]:
[4, 10, 18]
In [5]:
help(multiply)
Help on function multiply in module __main__:

multiply(a, b)
    This function returns the element-wise multiplication of the passed lists 'a' and 'b'

The documentation string should be placed between two sets of triple quotes ("""). This is a convention that makes it easier to expand the documentation later, and that ensures that nothing you write in the documentation will be expanded or interpreted as Python.

Documentation should provide an easy to understand, and brief description of what the function does. It should not give information that is obvious by reading the function signature. For example, this is a bad piece of documentation.

In [6]:
def multiply(a, b):
    """function multiply(a,b) -> list"""
    c = []
    for x,y in zip(a,b):
        c.append(x*y)
    return c
In [7]:
help(multiply)
Help on function multiply in module __main__:

multiply(a, b)
    function multiply(a,b) -> list

It is much better to say what the function does, and then what it returns (as this can't be seen from the signature). Good documentation would be

In [8]:
def multiply(a, b):
    """Calculates the element-wise multiplication of a and b, returning a list of the results"""
    c = []
    for x,y in zip(a,b):
        c.append(x*y)
    return c
In [9]:
help(multiply)
Help on function multiply in module __main__:

multiply(a, b)
    Calculates the element-wise multiplication of a and b, returning a list of the results

Your documentation can span over multiple lines. If you are describing the arguments, then you should use one line per argument, for example

In [10]:
def make_complex(real, imag=0):
    """Create and return a complex number
    
       Keyword arguments:
       
       real -- the real part of the number
       imag -- the imaginary part of the number
    """
    return (real,imag)
In [11]:
help(make_complex)
Help on function make_complex in module __main__:

make_complex(real, imag=0)
    Create and return a complex number
    
    Keyword arguments:
    
    real -- the real part of the number
    imag -- the imaginary part of the number

By convention, you will notice above that the last """ is placed on its own line if the documentation spans multiple lines. It is on the same line if the documentation is short.

In general, keep your documentation short, to the point, and avoid repeating obvious information. However, be precise, as this may be the only part of your code that somebody else reads before they use your function in their program.

A good suggestion is to look at documentation you like and try to copy that style. Also, look for code that you think is poorly documented, and try to avoid their mistakes.

Exercise

Below is a series of undocumented functions. Take a look through the functions and try to work out what they do. Once you understand the functions, write some documentation for each function. Get your neighbour to read your documentation. Do they understand what the function does based on what you have written? Do the function names combined with your documentation accurately convey the result of calling the function?

Note that you may have to use help(...) yourself if you don't recognise some of the code in the functions. Also try to play with the function to see how it behaves.

In [12]:
def add(a, b):
    """Calculates the element-wise sum of a and b, returning a list of the results"""
    c = []
    for x,y in zip(a,b):
        c.append(x+y)
    return c
In [13]:
help(add)
Help on function add in module __main__:

add(a, b)
    Calculates the element-wise sum of a and b, returning a list of the results

In [14]:
def subtract(a, b):
    """Calculates the element-wise ratio of a and b, returning a list of results. This function
       is badly named, as it DOES NOT return the element-wise difference of a and b!"""
    c = []
    for x,y in zip(a,b):
        c.append(x / y)
    return c
In [15]:
help(subtract)
Help on function subtract in module __main__:

subtract(a, b)
    Calculates the element-wise ratio of a and b, returning a list of results. This function
    is badly named, as it DOES NOT return the element-wise difference of a and b!

In [16]:
def capitalise(message):
    """Capitalises every word in message, returning the result"""
    words = message.split(" ")
    for i in range(0,len(words)):
        words[i] = "%s%s" % (words[i][0].upper(), words[i][1:])
    return " ".join(words)
In [17]:
help(capitalise)
Help on function capitalise in module __main__:

capitalise(message)
    Capitalises every word in message, returning the result

In [18]:
def surprise(x):
    """Prints 'Surprise!' if x is less than a random number between 0 and 1. Returns nothing"""
    import random
    if x < random.random():
        print("Surprise!")
In [19]:
help(surprise)
Help on function surprise in module __main__:

surprise(x)
    Prints 'Surprise!' if x is less than a random number between 0 and 1. Returns nothing

For this last function, try calling it via list_interface("ipynb").

In [20]:
def list_interface(x):
    """Returns a list of all files in the current directory that start with '0' and end with x"""
    import glob
    f = glob.glob("*.%s" % x)
    l = []
    for x in f:
        if x.startswith("0"):
            l.append(x)
    return l
In [21]:
help(list_interface)
Help on function list_interface in module __main__:

list_interface(x)
    Returns a list of all files in the current directory that start with '0' and end with x

In [ ]: