En desarrollo de software moderno, documentar es dejar constancia de la funcionalidad de una API. El propósito de la documentación moderna no es explicar las líneas de código, el código debería ser autoexplicativo.
El propósito de la documentación cambia en función de la audiencia. Para el usuario, la documentación debe dejar claro cómo usar y qué esperar de un software. Para los desarrolladores, la documentación debe reducir la carga cognitiva que supone analizar una unidad de código, agilizando el proceso de evolución del software, como la creación de nueva funcionalidad o la "refactorización" de la ya existente.
- Write The Docs es un portal web que pone énfasis en las buenas prácticas para escribir documentación.
La cadena de texto que encabeza un módulo, o la que sigue a una clase, un método o una función se denomina cadena de documentación o docstring. Normalmente, se delimitada por tres dobles comillas y es reconocida por el intérprete de Python que hace de ella un atributo del objeto al que documenta:
-
Crea un proyecto nuevo, crea un nuevo paquete
files
, y dentro, añade el siguiente código al ficheromemoryfiles.py
(no olvides crear el fichero__init__.py
dentro del paquetefiles
):"""Contain in-memory-files-related functionality.""" class MemoryFile: """Represent a file that only exists in memory.""" min_size = 4096 """Minimum size of the memory file in bytes.""" def __init__(self, name=''): """Create a new memory file with an optional name.""" self.name = name """Hold the name of the memory file.""" def read(self, count=1024): """Read some bytes from the file into a bytes object."""
Observa cómo documentamos los distintos objetos del módulo:
- La primera línea es una descripción corta del objeto.
- El tiempo verbal es el imperativo puesto que la documentación prescribe lo que el objeto debe hacer.
- Comienza por mayúscula y termina en punto.
- Si hay más documentación, esta continúa tras dejar una línea en blanco y se sangra al mismo nivel que el las comillas iniciales.
Las cadenas que acompañan a
min_size
yself.name = ...
no son docstrings, pero el PEP 257, que introduce la idea de este tipo de documentación, recomienda este estilo de cara a que herramientas de extracción de documentación ascie las cadenas como la documentación de atributos de clase e instancia, respectivamente. -
Inicia un intérprete interactivo, importa el módulo e inspecciona las distintas cadenas de documentación:
from files import memoryfiles memoryfiles.__doc__ memoryfiles.MemoryFile.__doc__ memoryfiles.MemoryFile.__init__.__doc__ memoryfiles.MemoryFile.read.__doc__
-
Haz lo mismo mediante la función
help()
:help(memoryfiles) help(memoryfiles.MemoryFile) help(memoryfiles.MemoryFile.__init__) help(memoryfiles.MemoryFile.read)
Pulsa
q
cuando quieras salir. -
Ahora sal del intérprete interactivo, y desde una terminal, ejecuta:
$ pydoc files
¿Qué observas? Sal de la utilidad pulsando
q
. -
También puedes explorar la documentación con un navegador, con la opción
-b
:$ pydoc -b files
La interfaz, de estética discutible, muestra una relación de todos los paquetes y módulos en las raíces de búsqueda. Bajo la sección punto, que representa el directorio actual, encontramos el módulo
docs
, sobre el que podemos hacer clic y encontrar una versión HTML de la misma ayuda que obtendríamos con la funciónhelp()
.Fíjate que esta UI incluye una utilidad de búsqueda en la esquina superior derecha.
La funcionalidad que inspecciona el fuente y extrae la documentación se
encuentra en el módulo pydoc
de la biblioteca estándar.
La función help()
y el módulo/comando pydoc
son insuficientes cuando
se quiere mostrar ayuda más rica y estructurada. Para cubrir esta necesidad
surgen los generadores de documentación.
Sphinx
es un proyecto de generación de documentación a partir de ficheros
reStructuredText o .rst
. Sphinx es, precisamente, el software que genera la
documentación de Python.
Sphinx es increiblemente potente, incluye soporte para múltiples lenguages, tematización, extensiones, internacionalización... En este tema, vamos a limitarnos al uso más sencillo.
-
Para comenzar a utilizar Sphinx, instálalo desde pip:
$ pip install sphinx
-
Crea la carpeta
docs
dentro de tu proyecto y entra en ella:$ mkdir docs
-
Ahora ejecuta el comando
sphinx-quickstart
y contesta a las preguntas pertinentes. Contestano
a querer tener los ficheros fuentes separados del directorio de salida de la documentación.El comando habrá creado un fichero
conf.py
y un documentoindex.rst
. -
Desde el directorio
docs
, ejecuta:$ sphinx-build -b html source _build/html
La documentación se habrá generado en el directorio
build
. Alternativamente, puedes correr el comando:$ make html
Que también generará la documentación sin necesidad de indicar explícitamente dónde se encuentran los fuentes y el destino.
-
Entra en la carpeta
_build
y levanta un servidor estático para ver la documentación en un navegador:$ python -m http.server
Abre el navegador y comprueba qué tal luce.
-
Ahora vuelve a
docs
, crea una carpeta nueva con el nombre deusage
y añade un documentoinstallation.rst
con el siguiente texto:Installation ============ In order to install this software, execute:: $ pip install files
-
Crea otro documento
quickstart.rst
con el siguiente texto:Quickstart ========== To start using memory files, use the `memoryfiles` module: .. code-block:: from files import memoryfiles memfile = memoryfiles.MemoryFile()
No te olvides del espacio tras los dos puntos seguidos y de la línea en blanco a continuación de
.. code-block::
. -
Ahora vuelve al nivel dónde se encuentra el fichero maestro
index.rst
, ábrelo, busca la directiva.. toctree::
y déjala de la siguiente forma:.. toctree:: :maxdepth: 2 :caption: Contents: usage/installation usage/quickstart
No olvides la línea en blanco a continuación de
:caption: Contents:
. -
Genera la documentación otra vez y comprueba los resultados en un navegador.
Como verás más formalmente, Sphinx usa un formato de documento llamado
reStructuredText, que es una suerte de lenguaje de marcado extensible. Las
directivas son la forma de extender el lenguaje. Un ejemplo de algunas
directivas son .. code-block::
o .. toctree::
.
Existen directivas para documentar objetos de Python. Por ejemplo, para
documentar la función de Python enumerate()
, podrías usar:
.. py:function:: enumerate(sequence[, start=0])
Return an iterator that yields tuples of an index and an item of the
*sequence*.
O la simplificación:
.. function:: enumerate(sequence[, start=0])
Return an iterator that yields tuples of an index and an item of the
*sequence*.
Esta forma omite el dominio de la directiva (el fragmento py:
que precedía a
function
).
Afortunadamente para nosotros, no es necesario escribir estas directivas a
mano. Sphinx es extensible y la extensión autodoc
permite generar estas
directivas a partir del fuente.
-
Activa la extensión modificando el fichero
conf.py
. Busca la variableextensions
y añade la cadena'sphinx.ext.autodoc'
:extensions = [ 'sphinx.ext.autodoc' ] autoclass_content = 'both'
La opción
autoclass_content
puesta a'both'
indica que se debe documentar el método__init__
como parte de la clase. -
La extensión importa los módulos que necesita documentar por lo que estos deben ser alcanzables desde el directorio donde se lanza la utilidad. Esto suele ser dentro de la carpeta
docs
por lo que hay que añadir el padre de esta carpeta asys.path
manualmente. Busca una sección enconf.py
que se llamaPath setup
, descomenta el código de ejemplo y modifícalo para que quede así:from os.path import dirname import sys sys.path.insert(0, dirname(dirname(__file__)))
-
Ve a la carpeta raíz del proyecto y utiliza el programa
sphinx-apidoc
para generar automáticamente las directivas deautodoc
:$ sphinx-apidoc -o docs/autodoc --separate --module-first files
Entra en la carpeta
docs/autodoc
y explora los ficheros generados.La extensión autodoc no es tan automática como pudiera parecer. Necesita que se escriban ciertas directivas. La herramienta
sphinx-apidoc
genera estas directivas recursivamente para todo un árbol de paquetes y módulos. -
Ahora tenemos que añadir los nuevos contenidos a la tabla de contenidos. Busca la directiva
.. toctree::
en el ficheroindex.rst
y asegúrate de que queda así:.. toctree:: :maxdepth: 2 :caption: Contents: usage/installation usage/quickstart API documentation <autodoc/modules>
-
Ahora puedes generar una nueva versión de la documentación. Dentro de la carpeta
docs
, lanza en una terminal:$ make html
Recuerda que la extension autodoc está realmente importando los módulos así que si hay código (más allá de definiciones) que se ejecute durante la carga del módulo, asegúrate que esté bajo la guarda:
if __name__ == '__main__': ...
O sé consciente de que se ejecutará.
- Sintáxis de la directiva
.. toctree::
*. - Documentación de la utilidad
sphinx-apidoc
*. - Opciones de configuración de autodoc.
El formato utilizado por Sphinx para documentar es reStructuredText o
.rst
. reStructuredText es considerablemente más rico que Markdown, por
ejemplo, y está soportado en GitHub. Lo normal, es que los proyectos Python
utilicen .rst
como su lenguaje de marcado por defecto.
Puedes consultar la sintáxis en esta
guía de reStructuredText
o en la página de referencia rápida .rst
de Sphinx.
.rst
es, además, extensible. La extensión del lenguaje tiene lugar a través
de las directivas. Una directiva tiene la siguiente forma:
.. nombre-de-la-directiva:: argumento-1 argumento-2
:parametro 1:
:parametro 2:
Contenido
A veces parámetros y contenido se invierten.
Sphinx añade múltiples directivas para la documentación de código a las que agrupa por lenguaje, llamadas "dominios". El dominio por defecto es Python.
En realidad, documentar un módulo sería hacer algo como:
Module `files.memoryfiles`
==========================
.. py:module:: files.memoryfiles
Contain in-memory-files-related functionality.
.. py:class:: files.memoryfiles.MemoryFile(name='')
Represent a file that only exists in memory.
:param name: The name of the memory file
:type name: str
.. py:attribute:: min_size
Minimum size of the memory file in bytes.
.. py:attribute:: name
Hold the name of the memory file.
.. py:method:: read(count=1024)
Read some bytes from the file into a bytes object.
:param count: The number of bytes to be read.
:type count: int
:return: The bytes read.
:rtype: bytes
Prueba a añadir el texto anterior a un fichero manual_file_doc.rst
, dentro
de la carpeta docs
y enlázalo en la tabla de contenidos. Genera una nueva
versión de la documentación y comparar la documentación generada por autodoc
con la escrita por tí manualmente. Cuando termines, borra la entrada en
la tabla de contenidos.
Afortunadamente, no nos tenemos que preocupar de la generación de estos
documentos .rst
porque autodoc ya lo hace por nosotros.
Una de las características más importantes de la buena documentación es el cruce de referencias que permite saltar al usuario entre los objetos relacionados. Además de los múltiples estilos de enlace, también podemos referenciar definiciones de Python.
Por ejemplo, modifiquemos la documentación del módulo para ilustrar el ejemplo:
"""Contain in-memory-files-related functionality.
The class :py:class:`MemoryFile` is the main class of the
module.
"""
Genera de nuevo la documentación para ver los resultados. Si quisieras
referirte a una clase o definición fuera del módulo, tendrías que especificar
el nombre completo de la definición
(en este ejemplo files.memoryfiles.MemoryFile
).
El modo por defecto para documentar un proyecto de Python es utilizar las directivas del dominio Python. La extensión autodoc simplifica considerablemente el proceso pero no lo evita totalmente, aun es necesario, por ejemplo, documentar la signatura de la función.
La forma por defecto de documentar con Sphinx es incrustando los parámetros
de las directivas en las docstrings de Python. Así, la documentación del
método read
sería:
"""Read some bytes from the file into a bytes object.
:param count: The number of bytes to be read.
:type count: int
:return: The read bytes.
:rtype: bytes
"""
Prueba a actualizar la documentación del módulo files.memoryfiles
y lanza
la generación de la documentación de nuevo.
Existen muchas opciones a la hora de documentar funciones y métodos. Al fin y al cabo, estos son los puntos de entrada a la funcionalidad de una API.
La sintáxis de Sphinx es un poco engorrosa. Podemos deshacernos de parte los parámetros con las anotaciones de nuestras funciones, métodos y atributos. Prueba a anotar los parámetros de los métodos de la clase, y sus atributos y lanza una nueva generación de la documentación.
Existen dos alternativas a la sintáxis de Sphinx que podemos utilizar gracias
a la extensión
napoleon
: el estilo NumPy y el estilo Google.
Para activar el módulo tendrás que añadirlo a la lista de extensiones:
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon'
]
Ahora puedes optar entre:
- Estilo Google (preferido): ejemplo completo.
- Estilo NumPy: ejemplo completo.
Mira, por ejemplo, cómo queda la documentación del módulo siguiendo el estilo de Google, con anotaciones:
"""Contain in-memory-files-related functionality.
The class :py:class:`files.memoryfiles.MemoryFile` is the main class of the
module.
"""
class MemoryFile:
"""Represent a file that only exists in memory.
Attributes:
min_size: Minimum size of the memory file in bytes.
name: Hold the name of the memory file.
"""
min_size: str = 4096
def __init__(self, name: str =''):
"""Create a new memory file with an optional name.
Args:
name: The name of the memory file.
"""
self.name = name # type: str
def read(self, count: str = 1024) -> bytes:
"""Read some bytes from the file into a bytes object.
Args:
count: Number of bytes to read.
"""
La extensión napoleon es realmente un paso de pre-procesamiento que traduce al dominio Python la sintaxis de cada uno de los estilos.
Antes de terminar, documenta lo que lleves de tu proyecto. Sigue los siguientes pasos, a modo de guía:
- Crea una carpeta
docs
en tu proyecto. - Ejecuta
sphinx-quickstart
como hiciste al comienzo de la lección. - Modifica
.gitignore
para que omita el directoriodocs/_build
. - Lanza una primera generación de la documentación.
- Añade documentación en ficheros
rst
con un corto tutorial de uso. - Lanza otra generación para comprobar que los nuevos cambios se han añadido.
- Elige un estilo de documentación: Sphinx, Google o NumPy.
- Documenta la API de tu proyecto, la privada y la pública.
- Utiliza
autodoc
para generar automáticamente la documentación de tu API. - Recuerda usar
napoleon
si elegiste un estilo distinto a Sphinx.