Have you ever experienced frustration because of the code you wrote? If you have been writing code for a while now, you probably had. And, if not, you certainly will. Sometimes we just keep staring at the code, trying to figure out what it does and why. When we wrote the code, everything was clear, but after a few weeks or months, it looks fuzzy. We can prevent this bad situation simply by writing Python Documentation. In this article, we are going to see how you can use python documentation best practices. These will make your code far more readable and maintainable.
Python Documentation Manifesto
What does “documenting” mean?
Documenting means add information to your code for other programmers to read. If you took coding classes, someone probably told you that writing code means talking to the PC. If you believe that, think again, because computers only understand binary: 0
and 1
.
You write code for people to read, not for computers.
Let this statement sink in your brain, as this can change the way you work. Even more, it can turn you from beginner to professional in no time, saving you all the pain in the road. You write code for people to read. It doesn’t matter if some other programmers will read your code, or if you are the only one working on it. At some point in time, you will have to read the code again. Thus, even if you are working alone, make a favor to the “you of the future”.
Documenting means adding an explanation to your code.
So, documenting means adding an explanation to your code. The program won’t use that for its functionality, but other programmers will use that to get by. Your should document every piece of code, even the ones you think obvious. Nothing is obvious when it comes to programming.
Python Documentation according to Best Practices
Every programming language supports documentation. Furthermore, every programming language has its own best practices when it comes to documentation. Python makes no exception, and we have some rules to follow when writing python documentation. In fact, the official Python website throws at you a list of things to do in this document. Probably not the easiest way to start writing documentation.
The most important concept you need to remember is that code and documentation must live in the same place. Writing the code and then creating a PDF document to explain its functionalities is a bad idea. Even more, it is one of the worst ideas we could have. By doing so, I guarantee that one day someone will edit the code and forget to update the document. This will create a gap between code and documentation that will eventually grow to make documentation useless.
Code and documentation must reside in the same place.
This is a modern concept of documentation, and many languages are adopting it. It simply means you write the documentation in the same file of the code. To be even more specific, you write the documentation inside the code you want to document. Documenting a function? Write its documentation inside the def
statement. Documenting a class? Write the documentation inside the class
statement, you get the point.
As we will see, Python does a really good job in that. Writing Python Documentation inside the code is ridiculously simple.
Writing Python Documentation
Introducing the docstring
Simply put, the docstring is a string containing the documentation of a given component. It is a special multiline string, thus you have to include it between three sets of quotes, not just one. Each individual component (a function, a class, a module) has its own docstring. If you don’t specify it, it will be None
. Each component can have only one docstring.
The docstring is a string containing the documentation of a given component.
Writing a docstring is simple, you just add the multiline string syntax right after the definition statement. No blank lines before, no blank lines after. Take a look at the following code, it shows you how to write the docstring in any circumstance.
"""This is the module docstring, as it is at the beginning of the file"""
class SampleClass:
"""This is the docstring for SampleClass"""
def sample_method(self):
"""This is the docstring for sample_method"""
pass
def sample_function:
"""This is the docstring of sample_function"""
pass
Dead simple. The docstring is not a comment, it is a string that the interpret will process. Furthermore, you can access the docstring with the __doc__
special attribute. If you want to print the documentation for SampleClass
, you could print SampleClass.__doc__
. If you want the one of sample_method
, print SampleClass.sample_method.__doc__
.
What to write inside a docstring
Writing Python documentation is extremely easy. However, writing meaningful python documentation requires more attention. You need to know what to write and what not to write.
Ideally, a docstring should explain what the function, class, or module does. It should not explain the way it works inside, but only how you can use the component from the outside. By reading the docstring, you should know how to use the function, class, or module it explains. You shouldn’t worry about how it works, only about how you can use it. Look at the following code.
def multiply(a, b):
"""Multiplies two numbers"""
return a * b
We can change the code inside the function to perform a sequence of additions, rather than a simple multiplication. Nonetheless, the output will be the same, and so does the input. Thus, the docstring is still valid if we rewrite the function as below.
def multiply(a, b):
"""Multiplies two integers"""
if a == 0 or b == 0:
return 0
res = 0
for i in range(b):
res += a
return res
Now, it is true that this function will not work if b
is negative, but we don’t care here. It is simply to show that docstring is useful when working outside the function.
Multiline Docstrings
Some components do serious stuff. They might perform advanced calculations, handle complex objects, and so on. For this kind of function, you might want to write a longer docstring. Not a problem, docstrings are natively multiline. However, you should write an overall description on a single line, leave a blank line, and then a more complex description.
def multiply(a, b):
"""Multiplies two integers
The first number does not have constraints, but the second number
cannot be negative. Doing otherwise leads to unexpected behaviour."""
if a == 0 or b == 0:
return 0
res = 0
for i in range(b):
res += a
return res
More often than not, you will be writing multiline docstring. We will see why in a minute.
What about comments?
In the Learn Python for Beginners in 1 day crash course, we introduced comments. Basically, if you write #
on a line, Python will ignore everything after that for that line. When it comes to Python documentation, comments are not docstrings. They have a different function.
A comment should never span multiple lines, and it should explain what the code is doing. Comments are your best friend when it comes to documenting the code inside a function.
Docstrings are for people using your component. Components are for people editing its code base.
Never use a comment when you should use a docstring, never use a docstring when you should use a comment. This is a golden rule that will help you to grow your code in a maintainable way. Docstrings are for people using your component, comments for people editing its code base.
def multiply(a, b):
"""Multiplies two integers
The first number does not have constraints, but the second number
cannot be negative.Doing otherwise leads to unexpected behaviour."""
# If any of the numbers is zero, result will be zero
if a == 0 or b == 0:
return 0
# Define a variable to store the result temporarily
res = 0
# Sum the a to the temporary result b times
for i in range(b):
# Update the temporary result
res += a
return res
Structured Text for Python Documentation
What is reStructured Text?
Who said your docstrings should contain boring text? They shouldn’t! In fact, you should write them with the reStructured Text (RST) syntax. RST is a way of writing text documents so that they remain readable as text documents, but can be parsed into something more graphical as well.
RST is a syntax for plain-text document that adds embellishments when processed with an RST reader. However, you can still read it as plain text.
For example, in RST, you can write *text*
to make the text bold. This doesn’t make your text ugly like adding HTML tags but allows it to be translated into beautiful HTML with a reader. You can read here about all the manipulations RST can do for you. If you are in a hurry (and who isn’t?), however, we reported the crucial parts right below.
RST Syntax for people in a hurry
Python documentation heavily relies on RST. This doesn’t mean you have to know it all, but knowing some is certainly good. This is what you will be adding all the time to your docstrings.
*text*
makes the text bold.**text**
makes the text italic.- You can combine the two above in
***text***
, which results in bold and italic.
You can also add an underline to your text with =
, *
and -
to make the text look like a title. Optionally, you can add also the same line above the text, the effect won’t change. However, the underline should be as long as the text, or even longer.
Heading
=======
Subheading
**********
Even smaller heading
--------------------
You can also use the special syntax :func:
to reference a function (or method), :class:
to reference a class and :mod:
to reference a module. After that, write the component name between special quotes as in the example below. When an RST parser will build the text, it will create a link to the component you are referencing. Much better than writing just the component name.
:mod:`mymodule`
:class:`mymodule.MyClass`
:func:`mymodule.MyClass.my_method`
:func:`mymodule.my_function`
Sometimes, things can look really ugly if you have a long dependency tree like mysupermodule.mysubmodule.MyClass.my_method
. Showing all of that is cumbersome, for no reason. We can avoid that with ~: adding this symbol at the beginning of the reference will show only the last item. For example, :func:`~mysupermodule.mysubmodule.MyClass.my_method`
will render simply as my_method
. Of course, the link will still point to the correct position.
Python Documentation of Parameters, Returns, and Errors
We know that at the core of Python Documentation we have the docstring. We also know that we should use it to explain how to use our function, module, or class. When talking about functions and methods, it makes sense to explain which parameters we expect and which returns we will give. Furthermore, if the function may generate an error, we want to tell which error may be raised and under which circumstances.
To do all of that, we use the special keywords :param:
, :type:
, :return:
, :rtype:
, and :raises:
. Specifically, we use them in this way.
:param parameter_name: description
allows you to define a description of the parameter.:type parameter_name: description
allows you to define a description for the type of value expected for the given parameter.:return: description
is for specifying a description of the value returned.:rtype: description
allows you to explain the type of value returned:raises Exception: description
allows you to describe when a specific exception is returned.
With these new definitions in mind, we can add some details about our multiply
function. Its docstring will look like this.
def multiply(a, b):
"""Multiplies two integers
The first number does not have constraints, but the second number
cannot be negative. Doing otherwise leads to unexpected behaviour.
:param a: First number
:type a: integer
:param b: Second number
:type b: positive integer
:returns: Multiplication of a and b
:rtype: integer"""
# If any of the numbers is zero, result will be zero
if a == 0 or b == 0:
return 0
# Define a variable to store the result temporarily
res = 0
# Sum the a to the temporary result b times
for i in range(b):
# Update the temporary result
res += a
return res
Sphinx
Awesome, now we know how to use RST in our documentation. We also know about the beauty of RST, which can render our docstrings into beautiful HTML where we actually see the text bold, not included in stars. Sphinx is the tool that does that – in fact, it does even more than that.
Sphinx reads your Python code and creates a set of static web pages containing all the documentation. It understands RST.
Using Sphinx is a pillar of python documentation, and being a complex tool we cannot cover everything about it in this article. However, we can get you going with it. If you wish to read more, you should take a look at the official Sphinx website.
Getting your Sphinx Up and Running
To use Sphinx in our Python Documentation, we need to install it. We can simply use pip
for that, as any other Python module.
pip install Sphinx
Now, navigate in your project folder and launch the Sphinx setup. This will create several folders that will hold the documentation.
sphinx-quickstart
It will ask you several questions, but the setup is guided and extremely easy. You can leave everything to the default value, but be sure to use the autodoc
module (by default, it won’t use it). You can change settings later, but it is a real pain.
At this point, open the generated conf.py file and after the first line (#!/usr/bin/env python3
) add the import of your project. To do that, you need to specify the project folder, in this case, it is .
because the project is in the same folder as Sphinx. For larger projects, you should have two separate folders for readability (e.g. sphinx
and source
).
import sys
sys.path.insert(0, ".")
Adding modules to Python Documentation
When we launched the Sphinx setup, it created several files. One of them is index.rst, an index file for your documentation. Guess what, it is written in RST. Here we can start adding the modules we want to include in our documentation with the automodule
keyword and the classes with the autoclass
keyword. So, open the file and add the following after :caption: Contents:
.
.. automodule:: mymodule
:members:
Of course, you need to use the name of your own module. This will look at the module and obtain its documentation. Without the :members:
option, it will only obtain the docstring of the module. However, by requiring the members, it will recursively search for its classes and their methods.
Building the Python Documentation
Once we want to finalize the static web pages with our documentation, we can run a build. We can do this as many times as we would like. Every time, it will take the documentation from the source code and create new pages, overriding the old ones. To run a build, you can run this code.
sphinx-build -b html
Instead of <source directory>
and <build directory>
, we obviously need to place where the source code is and where we want our documentation to be as output. After the build, you can open the index.html
file in your build directory in a browser. Our looks something like this.
Conclusion
Documentation is mandatory for modern programming. It is not an option, something you can add later. Thus, you should write it as you write your code. If you don’t, you will end up with too much documentation left behind, and sooner or later your code will collapse. We call this technical debt.
All professionals understand the negative impact technical debt can have on their code, so they avoid it with documentation. Now, you know how to write Python Documentation like a professional. Even if you are relatively new to the language, you know the fundamentals of writing good code. Teach yourself to write documentation even in the scripts you write for testing and learning, you will soon be a professional.
Now, it is time for you to explore all the goodness of documentation. Let me know what you think about it, about Sphinx and about RST. And, of course, let me know if you wish to know more about these topics! What are your thoughts? Just write them in the comments.