I am using Sphinx to document one of my projects and one of the classes takes in a function as a parameter in its __init__
. Is there a standard way to document this function-type parameter? I am also using sphinx.ext.napoleon
to use Google formatting for my docstrings.
Here's an example:
class ExampleClass:
"""An example class to demonstrate my question
Args:
func (what goes here?): Description of the parameter
"""
def __init__(self, func):
self.func = func
def do_something(self, param):
"""An example method
Args:
param (str): Description of param
Returns:
bool: The return description
"""
return self.func(param)
In my code, the parameter in question should take in one str
and return a bool
. Is there a standard way to document this when using Sphinx?
The simplest way to document the function parameter is using typing.Callable
. By doing so the static type checker will verify that the signature (arguments and return type) of the passed function is compatible with the type hint.
from typing import Callable
class ExampleClassTwo:
"""An example class to demonstrate my question.
Args:
func (Callable[[str], bool]): Description of the parameter.
"""
def __init__(self, func: Callable[[str], bool]):
self.func = func
However, this has one potential problem. If you look at the Python Data Model, in the sub-section titled "Callable types" under 3.2 The standard type hierarchy. You'll notice there are several possible types that are callables, and any callable with the specified signature would not cause a warning from the static type checker.
For example, a class instance implementing the __call__()
method would not cause a warning:
def str_function(param: str) -> bool:
pass
class OneInstance:
def __init__(self, param):
pass
def __call__(self, param: str) -> bool:
pass
one_instance = OneInstance("test instance")
# neither of the below raise a static type check warning
one = ExampleClassTwo(str_function)
two = ExampleClassTwo(one_instance)
You can type hint the param
signature as shown above together with validating that param
is of type FunctionType
in the __init__
as you would validate other parameters at run-time, and raise a TypeError
exception if the parameter is not a function.
from typing import Callable
from types import FunctionType
class ExampleClassTwo:
"""An example class to demonstrate my question
Args:
func (Callable[[str], bool]): Description of the parameter
Raises:
TypeError: Argument `param` is not of type ``FunctionType``.
"""
def __init__(self, func: Callable[[str], bool]):
if type(func) in (FunctionType,):
self.func = func
else:
raise TypeError("param must be initialized as being of ``FunctionType``.")
It may be possible to combine both requirements Callable[[str], bool]
and FunctionType
as an intersection using structural subtyping, I have not yet tried that approach.
Finally some examples are included that would cause the static type checker to raise warnings:
def int_function(param: int) -> bool:
pass
class ExampleClass:
"""An example class to demonstrate my question
Args:
func (FunctionType): Description of the parameter
"""
def __init__(self, func: FunctionType):
self.func = func
three = ExampleClass(str_function("test string")) # Expected type 'FunctionType', got 'bool' instead
four = ExampleClass(str_function) # Expected type 'FunctionType', got '(param: str) -> bool' instead
five = ExampleClass(type(str_function)) # No warning five.func is {type} <class 'function'>
six = ExampleClassTwo(int_function(2)) # Expected type '(str) -> bool', got 'bool' instead
seven = ExampleClassTwo(str_function("test string")) # Expected type '(str) -> bool', got 'bool' instead
eight = ExampleClassTwo(int_function) # Expected type '(str) -> bool', got '(param: int) -> bool' instead
nine = ExampleClassTwo(str_function) # No warning