How to document a method with parameter(s)?
Question:
How to document methods with parameters using Python’s documentation strings?
EDIT:
PEP 257 gives this example:
def complex(real=0.0, imag=0.0):
"""Form a complex number.
Keyword arguments:
real -- the real part (default 0.0)
imag -- the imaginary part (default 0.0)
"""
if imag == 0.0 and real == 0.0: return complex_zero
...
Is this the convention used by most Python developers ?
Keyword arguments:
<parameter name> -- Definition (default value if any)
I was expecting something a little bit more formal such as
def complex(real=0.0, imag=0.0):
"""Form a complex number.
@param: real The real part (default 0.0)
@param: imag The imaginary part (default 0.0)
"""
if imag == 0.0 and real == 0.0: return complex_zero
...
Environment: Python 2.7.1
Answers:
Conventions:
Tools:
- Epydoc: Automatic API Documentation Generation for Python
- sphinx.ext.autodoc – Include documentation from docstrings
- PyCharm has some nice support for docstrings
Update: Since Python 3.5 you can use type hints which is a compact, machine-readable syntax:
from typing import Dict, Union
def foo(i: int, d: Dict[str, Union[str, int]]) -> int:
"""
Explanation: this function takes two arguments: `i` and `d`.
`i` is annotated simply as `int`. `d` is a dictionary with `str` keys
and values that can be either `str` or `int`.
The return type is `int`.
"""
The main advantage of this syntax is that it is defined by the language and that it’s unambiguous, so tools like PyCharm can easily take advantage from it.
python doc strings are free-form, you can document it in any way you like.
Examples:
def mymethod(self, foo, bars):
"""
Does neat stuff!
Parameters:
foo - a foo of type FooType to bar with.
bars - The list of bars
"""
Now, there are some conventions, but python doesn’t enforce any of them. Some projects have their own conventions. Some tools to work with docstrings also follow specific conventions.
Docstrings are only useful within interactive environments, e.g. the Python shell. When documenting objects that are not going to be used interactively (e.g. internal objects, framework callbacks), you might as well use regular comments. Here’s a style I use for hanging indented comments off items, each on their own line, so you know that the comment is applying to:
def Recomputate
(
TheRotaryGyrator,
# the rotary gyrator to operate on
Computrons,
# the computrons to perform the recomputation with
Forthwith,
# whether to recomputate forthwith or at one's leisure
) :
# recomputates the specified rotary gyrator with
# the desired computrons.
...
#end Recomputate
You can’t do this sort of thing with docstrings.
Based on my experience, the numpy docstring conventions (PEP257 superset) are the most widely-spread followed conventions that are also supported by tools, such as Sphinx.
One example:
Parameters
----------
x : type
Description of parameter `x`.
If you plan to use Sphinx to document your code, it is capable of producing nicely formatted HTML docs for your parameters with their ‘signatures’ feature. http://sphinx-doc.org/domains.html#signatures
The mainstream is, as other answers here already pointed out, probably going with the Sphinx way so that you can use Sphinx to generate those fancy documents later.
That being said, I personally go with inline comment style occasionally.
def complex( # Form a complex number
real=0.0, # the real part (default 0.0)
imag=0.0 # the imaginary part (default 0.0)
): # Returns a complex number.
"""Form a complex number.
I may still use the mainstream docstring notation,
if I foresee a need to use some other tools
to generate an HTML online doc later
"""
if imag == 0.0 and real == 0.0:
return complex_zero
other_code()
One more example here, with some tiny details documented inline:
def foo( # Note that how I use the parenthesis rather than backslash ""
# to natually break the function definition into multiple lines.
a_very_long_parameter_name,
# The "inline" text does not really have to be at same line,
# when your parameter name is very long.
# Besides, you can use this way to have multiple lines doc too.
# The one extra level indentation here natually matches the
# original Python indentation style.
#
# This parameter represents blah blah
# blah blah
# blah blah
param_b, # Some description about parameter B.
# Some more description about parameter B.
# As you probably noticed, the vertical alignment of pound sign
# is less a concern IMHO, as long as your docs are intuitively
# readable.
last_param, # As a side note, you can use an optional comma for
# your last parameter, as you can do in multi-line list
# or dict declaration.
): # So this ending parenthesis occupying its own line provides a
# perfect chance to use inline doc to document the return value,
# despite of its unhappy face appearance. :)
pass
The benefits (as @mark-horvath already pointed out in another comment) are:
- Most importantly, parameters and their doc always stay together, which brings the following benefits:
- Less typing (no need to repeat variable name)
- Easier maintenance upon changing/removing variable. There will never be some orphan parameter doc paragraph after you rename some parameter.
- and easier to find missing comment.
Now, some may think this style looks “ugly”. But I would say “ugly” is a subjective word. A more neutual way is to say, this style is not mainstream so it may look less familiar to you, thus less comfortable. Again, “comfortable” is also a subjective word. But the point is, all the benefits described above are objective. You can not achieve them if you follow the standard way.
Hopefully some day in the future, there will be a doc generator tool which can also consume such inline style. That will drive the adoption.
PS: This answer is derived from my own preference of using inline comments whenever I see fit. I use the same inline style to document a dictionary too.
Since docstrings are free-form, it really depends on what you use to parse code to generate API documentation.
I would recommend getting familiar with the Sphinx markup, since it is widely used and is becoming the de-facto standard for documenting Python projects, in part because of the excellent readthedocs.org service. To paraphrase an example from the Sphinx documentation as a Python snippet:
def send_message(sender, recipient, message_body, priority=1) -> int:
"""
Send a message to a recipient.
:param str sender: The person sending the message
:param str recipient: The recipient of the message
:param str message_body: The body of the message
:param priority: The priority of the message, can be a number 1-5
:type priority: integer or None
:return: the message id
:rtype: int
:raises ValueError: if the message_body exceeds 160 characters
:raises TypeError: if the message_body is not a basestring
"""
This markup supports cross-referencing between documents and more. Note that the Sphinx documentation uses (e.g.) :py:attr:
whereas you can just use :attr:
when documenting from the source code.
Naturally, there are other tools to document APIs. There’s the more classic Doxygen which uses param
commands but those are not specifically designed to document Python code like Sphinx is.
Note that there is a similar question with a similar answer in here…
Building upon the type-hints answer (https://stackoverflow.com/a/9195565/2418922), which provides a better structured way to document types of parameters, there exist also a structured manner to document both type and descriptions of parameters:
def copy_net(
infile: (str, 'The name of the file to send'),
host: (str, 'The host to send the file to'),
port: (int, 'The port to connect to')):
pass
example adopted from: https://pypi.org/project/autocommand/
Sphinx Python 3 typing minimal runnable example
Just to make what other answers said more concrete:
build.sh
sphinx-build . out
conf.py
import os
import sys
sys.path.insert(0, os.path.abspath('.'))
extensions = [ 'sphinx.ext.autodoc' ]
autodoc_default_options = {
'members': True,
'show-inheritance': True,
}
autodoc_typehints = "description"
index.rst
.. automodule:: main
main.py
class MyClass:
"""
This class does that.
"""
def __init__(self, i):
self.i = i
def do_this(parameter1: int, parameter2: MyClass) -> int:
"""
This function does this.
:param parameter1: my favorite int
:param parameter2: my favorite class
:return: what it returns
"""
return parameter1 + parameter2.i
requirements.txt
Sphinx==6.1.3
Output on out/index.html
:
If we remove:
autodoc_typehints = "description"
typing types show on the function signature instead:
Pre-typing equivalent
The following produces the same output as the version with autodoc_typehints = "description"
, but with a bit more parameter name repetition:
def do_this(parameter1, parameter2):
"""
This function does this.
:param parameter1: my favorite int
:type parameter1: int
:param parameter2: my favorite class
:type parameter2: MyClass
:return: what it returns
:rtype: int
"""
return parameter1 + parameter2.i
The dream: argument docstrings
It is bad that we have to retype argument names for every :param argumentname:
. There doesn’t seem to be a good solution to this currently. Some ways it might be solved in the future:
-
Sphinx could add #:
doc comments to parameters. This Sphinx extension already works for instance attributes as mentioned at: How to show instance attributes in sphinx doc?
class MyClass:
def __init__(self, par1: int):
#: My favorite attribute!
self.var1: int = par1
so why not give us:
def do_this(
#: my favorite int
parameter1: int,
#: my favorite class
parameter2: MyClass
) -> int:
-
Python could finally actually support argument docstrings natively!
def do_this(
parameter1: int,
"""
my favorite int
"""
parameter2: MyClass
"""
my favorite class
"""
) -> int:
TODO find feature requests for these!
Same desire also mentioned at: https://stackoverflow.com/a/39581355/895245
Other useful typing related things to know
- how to represent multiple types: How to express multiple types for a single parameter or a return value in docstrings that are processed by Sphinx?
- how to link to the documentation of types of external projects: Specifying targets for intersphinx links to numpy, scipy, and matplotlib
Tested on Python 3.10, Ubuntu 22.10.
How to document methods with parameters using Python’s documentation strings?
EDIT:
PEP 257 gives this example:
def complex(real=0.0, imag=0.0):
"""Form a complex number.
Keyword arguments:
real -- the real part (default 0.0)
imag -- the imaginary part (default 0.0)
"""
if imag == 0.0 and real == 0.0: return complex_zero
...
Is this the convention used by most Python developers ?
Keyword arguments:
<parameter name> -- Definition (default value if any)
I was expecting something a little bit more formal such as
def complex(real=0.0, imag=0.0):
"""Form a complex number.
@param: real The real part (default 0.0)
@param: imag The imaginary part (default 0.0)
"""
if imag == 0.0 and real == 0.0: return complex_zero
...
Environment: Python 2.7.1
Conventions:
Tools:
- Epydoc: Automatic API Documentation Generation for Python
- sphinx.ext.autodoc – Include documentation from docstrings
- PyCharm has some nice support for docstrings
Update: Since Python 3.5 you can use type hints which is a compact, machine-readable syntax:
from typing import Dict, Union
def foo(i: int, d: Dict[str, Union[str, int]]) -> int:
"""
Explanation: this function takes two arguments: `i` and `d`.
`i` is annotated simply as `int`. `d` is a dictionary with `str` keys
and values that can be either `str` or `int`.
The return type is `int`.
"""
The main advantage of this syntax is that it is defined by the language and that it’s unambiguous, so tools like PyCharm can easily take advantage from it.
python doc strings are free-form, you can document it in any way you like.
Examples:
def mymethod(self, foo, bars):
"""
Does neat stuff!
Parameters:
foo - a foo of type FooType to bar with.
bars - The list of bars
"""
Now, there are some conventions, but python doesn’t enforce any of them. Some projects have their own conventions. Some tools to work with docstrings also follow specific conventions.
Docstrings are only useful within interactive environments, e.g. the Python shell. When documenting objects that are not going to be used interactively (e.g. internal objects, framework callbacks), you might as well use regular comments. Here’s a style I use for hanging indented comments off items, each on their own line, so you know that the comment is applying to:
def Recomputate
(
TheRotaryGyrator,
# the rotary gyrator to operate on
Computrons,
# the computrons to perform the recomputation with
Forthwith,
# whether to recomputate forthwith or at one's leisure
) :
# recomputates the specified rotary gyrator with
# the desired computrons.
...
#end Recomputate
You can’t do this sort of thing with docstrings.
Based on my experience, the numpy docstring conventions (PEP257 superset) are the most widely-spread followed conventions that are also supported by tools, such as Sphinx.
One example:
Parameters
----------
x : type
Description of parameter `x`.
If you plan to use Sphinx to document your code, it is capable of producing nicely formatted HTML docs for your parameters with their ‘signatures’ feature. http://sphinx-doc.org/domains.html#signatures
The mainstream is, as other answers here already pointed out, probably going with the Sphinx way so that you can use Sphinx to generate those fancy documents later.
That being said, I personally go with inline comment style occasionally.
def complex( # Form a complex number
real=0.0, # the real part (default 0.0)
imag=0.0 # the imaginary part (default 0.0)
): # Returns a complex number.
"""Form a complex number.
I may still use the mainstream docstring notation,
if I foresee a need to use some other tools
to generate an HTML online doc later
"""
if imag == 0.0 and real == 0.0:
return complex_zero
other_code()
One more example here, with some tiny details documented inline:
def foo( # Note that how I use the parenthesis rather than backslash ""
# to natually break the function definition into multiple lines.
a_very_long_parameter_name,
# The "inline" text does not really have to be at same line,
# when your parameter name is very long.
# Besides, you can use this way to have multiple lines doc too.
# The one extra level indentation here natually matches the
# original Python indentation style.
#
# This parameter represents blah blah
# blah blah
# blah blah
param_b, # Some description about parameter B.
# Some more description about parameter B.
# As you probably noticed, the vertical alignment of pound sign
# is less a concern IMHO, as long as your docs are intuitively
# readable.
last_param, # As a side note, you can use an optional comma for
# your last parameter, as you can do in multi-line list
# or dict declaration.
): # So this ending parenthesis occupying its own line provides a
# perfect chance to use inline doc to document the return value,
# despite of its unhappy face appearance. :)
pass
The benefits (as @mark-horvath already pointed out in another comment) are:
- Most importantly, parameters and their doc always stay together, which brings the following benefits:
- Less typing (no need to repeat variable name)
- Easier maintenance upon changing/removing variable. There will never be some orphan parameter doc paragraph after you rename some parameter.
- and easier to find missing comment.
Now, some may think this style looks “ugly”. But I would say “ugly” is a subjective word. A more neutual way is to say, this style is not mainstream so it may look less familiar to you, thus less comfortable. Again, “comfortable” is also a subjective word. But the point is, all the benefits described above are objective. You can not achieve them if you follow the standard way.
Hopefully some day in the future, there will be a doc generator tool which can also consume such inline style. That will drive the adoption.
PS: This answer is derived from my own preference of using inline comments whenever I see fit. I use the same inline style to document a dictionary too.
Since docstrings are free-form, it really depends on what you use to parse code to generate API documentation.
I would recommend getting familiar with the Sphinx markup, since it is widely used and is becoming the de-facto standard for documenting Python projects, in part because of the excellent readthedocs.org service. To paraphrase an example from the Sphinx documentation as a Python snippet:
def send_message(sender, recipient, message_body, priority=1) -> int:
"""
Send a message to a recipient.
:param str sender: The person sending the message
:param str recipient: The recipient of the message
:param str message_body: The body of the message
:param priority: The priority of the message, can be a number 1-5
:type priority: integer or None
:return: the message id
:rtype: int
:raises ValueError: if the message_body exceeds 160 characters
:raises TypeError: if the message_body is not a basestring
"""
This markup supports cross-referencing between documents and more. Note that the Sphinx documentation uses (e.g.) :py:attr:
whereas you can just use :attr:
when documenting from the source code.
Naturally, there are other tools to document APIs. There’s the more classic Doxygen which uses param
commands but those are not specifically designed to document Python code like Sphinx is.
Note that there is a similar question with a similar answer in here…
Building upon the type-hints answer (https://stackoverflow.com/a/9195565/2418922), which provides a better structured way to document types of parameters, there exist also a structured manner to document both type and descriptions of parameters:
def copy_net(
infile: (str, 'The name of the file to send'),
host: (str, 'The host to send the file to'),
port: (int, 'The port to connect to')):
pass
example adopted from: https://pypi.org/project/autocommand/
Sphinx Python 3 typing minimal runnable example
Just to make what other answers said more concrete:
build.sh
sphinx-build . out
conf.py
import os
import sys
sys.path.insert(0, os.path.abspath('.'))
extensions = [ 'sphinx.ext.autodoc' ]
autodoc_default_options = {
'members': True,
'show-inheritance': True,
}
autodoc_typehints = "description"
index.rst
.. automodule:: main
main.py
class MyClass:
"""
This class does that.
"""
def __init__(self, i):
self.i = i
def do_this(parameter1: int, parameter2: MyClass) -> int:
"""
This function does this.
:param parameter1: my favorite int
:param parameter2: my favorite class
:return: what it returns
"""
return parameter1 + parameter2.i
requirements.txt
Sphinx==6.1.3
Output on out/index.html
:
If we remove:
autodoc_typehints = "description"
typing types show on the function signature instead:
Pre-typing equivalent
The following produces the same output as the version with autodoc_typehints = "description"
, but with a bit more parameter name repetition:
def do_this(parameter1, parameter2):
"""
This function does this.
:param parameter1: my favorite int
:type parameter1: int
:param parameter2: my favorite class
:type parameter2: MyClass
:return: what it returns
:rtype: int
"""
return parameter1 + parameter2.i
The dream: argument docstrings
It is bad that we have to retype argument names for every :param argumentname:
. There doesn’t seem to be a good solution to this currently. Some ways it might be solved in the future:
-
Sphinx could add
#:
doc comments to parameters. This Sphinx extension already works for instance attributes as mentioned at: How to show instance attributes in sphinx doc?class MyClass: def __init__(self, par1: int): #: My favorite attribute! self.var1: int = par1
so why not give us:
def do_this( #: my favorite int parameter1: int, #: my favorite class parameter2: MyClass ) -> int:
-
Python could finally actually support argument docstrings natively!
def do_this( parameter1: int, """ my favorite int """ parameter2: MyClass """ my favorite class """ ) -> int:
TODO find feature requests for these!
Same desire also mentioned at: https://stackoverflow.com/a/39581355/895245
Other useful typing related things to know
- how to represent multiple types: How to express multiple types for a single parameter or a return value in docstrings that are processed by Sphinx?
- how to link to the documentation of types of external projects: Specifying targets for intersphinx links to numpy, scipy, and matplotlib
Tested on Python 3.10, Ubuntu 22.10.