def add_np(a:int, b:int=0)->int:
"""The sum of two numbers.
Used to demonstrate numpy-style docstrings.
Parameters
----------
a : int
the 1st number to add
b : int
the 2nd number to add (default: 0)
Returns
-------
int
the result of adding `a` to `b`"""
return a+bDocments
A docment is a comment beside a parameter in a function signature. It keeps the parameter’s description next to its name, type and default. docments(f) reads these comments for documentation tools and CLIs such as fastcore.script:
Why?
Without docments, if you want to document your parameters, you have to repeat param names in docstrings, since they’re already in the function signature. The parameters have to be kept synchronized in the two places as you change your code. Readers of your code have to look back and forth between two places to understand what’s happening. So it’s more work for you, and for your users.
NumPy-style docstrings have formatting rules, including where to put spaces before colons. That formatting is a pain to write and maintain, and awkward to read in code. Here’s an example using NumPy-style documentation:
By comparison, here’s the same thing using docments:
def add(
a:int, # the 1st number to add
b=0, # the 2nd number to add
)->int: # the result of adding `a` to `b`
"The sum of two numbers."
return a+bNumpy docstring helper functions
docments also reads NumPy-style docstrings. You can combine them with parameter comments. The helpers below read and parse the docstrings.
docstring
def docstring(
sym
):Get documentation from an object, its constructor, or its callable implementation
docstring reads the function’s prose independently of its parameter comments.
test_eq(docstring(add), "The sum of two numbers.")Classes without their own prose can use a documented parent constructor. An undocumented class does not inherit the generic object.__init__ description.
class Named:
def __init__(self, name):
"Create a named object."
self.name = name
class NamedChild(Named): pass
class Undocumented: pass
test_eq(docstring(NamedChild), "Create a named object.")
test_eq(docstring(Undocumented), '')
docstring(NamedChild)'Create a named object.'
NumPy parameter sections become named records with a type and description. The summary remains separate from those parameter descriptions.
npdocs = parse_docstring(add_np)
test_eq(npdocs.Parameters['a'].type, 'int')
npdocs.Parameters{'a': Parameter(name='a', type='int', desc=['the 1st number to add']),
'b': Parameter(name='b', type='int', desc=['the 2nd number to add (default: 0)'])}
get_source
def get_source(
s
):Get source for a function, callable implementation, dataclass, or source string
get_source unwraps decorated functions and partials to find their implementation. Passing source text leaves it unchanged.
source = get_source(add)
test_eq(get_source(partial(add, 1)), source)
test_eq(get_source(source), source)
PrettyString(source)def add(
a:int, # the 1st number to add
b=0, # the 2nd number to add
)->int: # the result of adding `a` to `b`
"The sum of two numbers."
return a+b
get_name uses the short function or method name. qual_name retains its enclosing class or module name.
test_eq(get_name(L.map), 'map')
test_eq(qual_name(L.map), 'L.map')
test_eq(qual_name(docscrape), 'fastcore.docscrape')
get_name(L.map), qual_name(L.map)('map', 'L.map')
Extracting parameter documentation
Follow one delegated function through comment extraction. _d supplies its own documentation for b; a and z come from the functions it delegates to:
def _b(
z:str='b', # Last
):
return b, a
@delegates(_b)
def _c(
b:str, # Ignore
a:int=2
): return b, a
@delegates(_c)
def _d(
c:int, # First
b:str, # Second
**kwargs
)->int: # Return an int
return c, _c(b, **kwargs)Python’s tokenizer retains the comments that the syntax tree omits. First collect each comment by source line:
s = _d
comments = {o.start[0]:_clean_comment(o.string) for o in _tokens(s) if o.type==COMMENT}
comments{3: ' First', 4: ' Second', 6: ' Return an int'}
Parameter locations use one-based source line numbers. The return annotation also has a location when present.
parms = _param_locs(s, returns=True, args_kwargs=True) or {}
parms{3: 'c', 4: 'b', 5: 'kwargs', 6: 'return'}
A parameter uses its trailing comment, or consecutive comment lines immediately above it. Comments belonging to another parameter are not included.
docs = {arg:_get_comment(line, arg, comments, parms) for line,arg in parms.items()}
test_eq(docs['c'], 'First')
docs{'c': 'First', 'b': 'Second', 'kwargs': None, 'return': 'Return an int'}
Full docments retain the signature’s annotation and default alongside the comment.
sig = signature(s, eval_str=True)
res = {name:_get_full(p, docs) for name,p in sig.parameters.items()}
test_is(res['c'].anno, int)
res{'c': {'docment': 'First',
'anno': int,
'default': inspect._empty,
'kind': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>},
'b': {'docment': 'Second',
'anno': str,
'default': inspect._empty,
'kind': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>},
'a': {'docment': None,
'anno': int,
'default': 2,
'kind': <_ParameterKind.KEYWORD_ONLY: 3>},
'z': {'docment': None,
'anno': str,
'default': 'b',
'kind': <_ParameterKind.KEYWORD_ONLY: 3>}}
Add the return annotation and its comment to res. _d.__delwrap__ points to _c, where the next pass looks for descriptions of delegated parameters.
res['return'] = AttrDict(docment=docs.get('return'), anno=sig.return_annotation, default=empty)
test_eq(res['return'].docment, 'Return an int')
res['return']{ 'anno': <class 'int'>,
'default': <class 'inspect._empty'>,
'docment': 'Return an int'}_d.__delwrap__<function __main__._c(b: str, a: int = 2, *, z: str = 'b')>
Docments
ann_parts
def ann_parts(
anno
):The underlying type and metadata tuple of an Annotated, else (anno, ())
ann_parts splits an annotation into its underlying type and its Annotated metadata, returning an empty tuple for plain annotations. An Annotated[None, ...] reports its type as None (used for CLI params whose argparse action takes no value).
test_eq(ann_parts(int), (int, ()))
test_eq(ann_parts(Annotated[int, "doc", dict(nargs='+')]), (int, ("doc", dict(nargs='+'))))
test_eq(ann_parts(Annotated[None, "ver"]), (None, ("ver",)))docments
def docments(
s, full:bool=False, eval_str:bool=False, returns:bool=True, args_kwargs:bool=False
):Get parameter and return documentation from comments, Annotated metadata, and NumPy docstrings
def add(
a:int, # The first operand
b=1, # The second operand
) -> int: # The sum
"Add `a` to `b`"
return a + b
test_eq(docments(add), {'a': 'The first operand', 'b': 'The second operand', 'return': 'The sum'})For longer descriptions, put comment lines above the parameter. docments also reads typing.Annotated string metadata and NumPy-style docstrings. Pass full=True to retrieve each parameter’s default, type and docment together.
docs = docments(_d)
test_eq(docs, {'c':'First', 'b':'Second', 'a':None, 'z':'Last', 'return':'Return an int'})
docs{'a': None, 'b': 'Second', 'c': 'First', 'return': 'Return an int', 'z': 'Last'}full = docments(_d, full=True)
test_eq({k:v.docment for k,v in full.items()}, docs)
test_eq(full.a.default, 2)
full{ 'a': { 'anno': <class 'int'>,
'default': 2,
'docment': None,
'kind': <_ParameterKind.KEYWORD_ONLY: 3>},
'b': { 'anno': <class 'str'>,
'default': <class 'inspect._empty'>,
'docment': 'Second',
'kind': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>},
'c': { 'anno': <class 'int'>,
'default': <class 'inspect._empty'>,
'docment': 'First',
'kind': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>},
'return': { 'anno': <class 'int'>,
'default': <class 'inspect._empty'>,
'docment': 'Return an int'},
'z': { 'anno': <class 'str'>,
'default': 'b',
'docment': 'Last',
'kind': <_ParameterKind.KEYWORD_ONLY: 3>}}Editing a module on disk can leave a live object’s source locations out of date. If that source no longer parses, docments returns signature information without comments. Restart the interpreter to load the edited module. Invalid source text passed directly to docments still raises a syntax error:
def _fn(a:int): return a
_real = get_source
def get_source(s): return ' "stale"\nx = 1'
try: test_eq(docments(_fn), {'a': None, 'return': None})
finally: get_source = _real
test_fail(lambda: _parses(' "stale"\nx = 1'), contains='indent')Instead of a comment, you can document a parameter or return value with typing.Annotated metadata. The first string in the metadata is used as the docment. A trailing comment wins if both are present. Generated APIs can carry these descriptions without Python source:
def _g(
a:Annotated[int, "the first"],
b:Annotated[str, "ignored"]='x' # the second
) -> Annotated[list[int], "the results"]: ...
test_eq(docments(_g), {'a': 'the first', 'b': 'the second', 'return': 'the results'})docments returns a dict mapping parameter names to descriptions. The return key holds the return description. Pass returns=False to omit that entry. Here it is for add:
def add(
a:int, # the 1st number to add
b=0, # the 2nd number to add
)->int: # the result of adding `a` to `b`
"The sum of two numbers."
return a+bdocments(add){ 'a': 'the 1st number to add',
'b': 'the 2nd number to add',
'return': 'the result of adding `a` to `b`'}args_kwargs=True adds args and kwargs docs too:
def add(
a:int, # the 1st number to add
*args, # some args
b=0, # the 2nd number to add
**kwargs, # Passed to the `example` function
)->int: # the result of adding `a` to `b`
"The sum of two numbers."
return a+bdocments(add, args_kwargs=True){ 'a': 'the 1st number to add',
'args': 'some args',
'b': 'the 2nd number to add',
'kwargs': 'Passed to the `example` function',
'return': 'the result of adding `a` to `b`'}If you pass full=True, the values are dict of defaults, types, and docments as values. Note that the type annotation is inferred from the default value, if the annotation is empty and a default is supplied. (Note that for full, args_kwargs=True is always set too.)
docments(add, full=True){ 'a': { 'anno': <class 'int'>,
'default': <class 'inspect._empty'>,
'docment': 'the 1st number to add',
'kind': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>},
'args': { 'anno': <class 'inspect._empty'>,
'default': <class 'inspect._empty'>,
'docment': 'some args',
'kind': <_ParameterKind.VAR_POSITIONAL: 2>},
'b': { 'anno': <class 'int'>,
'default': 0,
'docment': 'the 2nd number to add',
'kind': <_ParameterKind.KEYWORD_ONLY: 3>},
'kwargs': { 'anno': <class 'inspect._empty'>,
'default': <class 'inspect._empty'>,
'docment': None,
'kind': <_ParameterKind.VAR_KEYWORD: 4>},
'return': { 'anno': <class 'int'>,
'default': <class 'inspect._empty'>,
'docment': 'the result of adding `a` to `b`'}}To evaluate stringified annotations (from python 3.10), use eval_str:
docments(add, full=True, eval_str=True)['a']{ 'anno': <class 'int'>,
'default': <class 'inspect._empty'>,
'docment': 'the 1st number to add',
'kind': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>}docments(add, full=True)['a']{ 'anno': <class 'int'>,
'default': <class 'inspect._empty'>,
'docment': 'the 1st number to add',
'kind': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>}If you need more space to document a parameter, place one or more lines of comments above the parameter, or above the return type. You can mix-and-match these docment styles:
def add(
# The first operand
a:int,
# This is the second of the operands to the *addition* operator.
# Note that passing a negative value here is the equivalent of the *subtraction* operator.
b:int,
)->int: # The result is calculated using Python's builtin `+` operator.
"Add `a` to `b`"
return a+bdocments(add){ 'a': 'The first operand',
'b': 'This is the second of the operands to the *addition* operator.\n'
'Note that passing a negative value here is the equivalent of the '
'*subtraction* operator.',
'return': "The result is calculated using Python's builtin `+` operator."}Docments works with async functions, too:
async def add_async(
# The first operand
a:int,
# This is the second of the operands to the *addition* operator.
# Note that passing a negative value here is the equivalent of the *subtraction* operator.
b:int,
)->int: # The result is calculated using Python's builtin `+` operator.
"Add `a` to `b`"
return a+btest_eq(docments(add_async), docments(add))You can also use docments with classes and methods:
class Adder:
"An addition calculator"
def __init__(self,
a:int, # First operand
b:int, # 2nd operand
): self.a,self.b = a,b
def calculate(self
)->int: # Integral result of addition operator
"Add `a` to `b`"
return a+bdocments(Adder){'a': 'First operand', 'b': '2nd operand', 'return': None, 'self': None}docments(Adder.calculate){'return': 'Integral result of addition operator', 'self': None}docments can also be extracted from numpy-style docstrings:
print(add_np.__doc__)The sum of two numbers.
Used to demonstrate numpy-style docstrings.
Parameters
----------
a : int
the 1st number to add
b : int
the 2nd number to add (default: 0)
Returns
-------
int
the result of adding `a` to `b`
docments(add_np){ 'a': 'the 1st number to add',
'b': 'the 2nd number to add (default: 0)',
'return': 'the result of adding `a` to `b`'}NumPy descriptions fill gaps in the parameter comments. When both document the same parameter, the comment takes precedence:
def add_mixed(
a:int, # the first number to add
b
)->int: # the result
"""The sum of two numbers.
Parameters
----------
a : int
description overridden by the parameter comment
b : int
the 2nd number to add (default: 0)"""
return a+bmixed = docments(add_mixed, full=True)
test_eq(mixed.a.docment, 'the first number to add')
test_eq(mixed.b.docment, 'the 2nd number to add (default: 0)')
mixed{ 'a': { 'anno': <class 'int'>,
'default': <class 'inspect._empty'>,
'docment': 'the first number to add',
'kind': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>},
'b': { 'anno': <class 'inspect._empty'>,
'default': <class 'inspect._empty'>,
'docment': 'the 2nd number to add (default: 0)',
'kind': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>},
'return': { 'anno': <class 'int'>,
'default': <class 'inspect._empty'>,
'docment': 'the result'}}docments reads dataclass field comments from the class’s source file. For a dataclass defined in a notebook, export it to a module and import the class from there.
Builtins just return an empty dict:
docments(str){'args': None, 'kwargs': None, 'return': None, 'self': None}Extract docstrings
sig_source preserves the original source lines through the signature. A body written on the same line is included; a body on subsequent lines is omitted.
print(sig_source(flexiclass))def flexiclass(
cls # The class to convert
) -> dataclass:
def simple(x: dict[str, int]): return x
print(sig_source(simple))def simple(x: dict[str, int]): return x
def multi(a, b=1,
c=2,
d=3):
return a
source = sig_source(multi)
assert ' c=2,' in source
assert 'return a' not in source
PrettyString(source)def multi(a, b=1,
c=2,
d=3):
extract_docstrings
def extract_docstrings(
code
):Create a dict from function/class/method names to tuples of docstrings and param lists
extract_docstrings reads source without executing it. It records public functions and methods, module prose, and a class’s constructor parameters. A constructor docstring supplies class documentation when the class has none.
sample_code = """
"This is a module."
def top_func(a, b, *args, **kw):
"This is top-level."
pass
class SampleClass:
"This is a class."
def __init__(self, x, y):
"Constructor for SampleClass."
pass
def method1(self, param1):
"This is method1."
pass
def _private_method(self):
"This should not be included."
pass
class AnotherClass:
def __init__(self, a, b):
"This class has no separate docstring."
pass"""
exp = {'_module': ('This is a module.', ''), 'top_func': ('This is top-level.', 'a, b, *args, **kw'),
'SampleClass': ('This is a class.', 'self, x, y'), 'SampleClass.method1': ('This is method1.', 'self, param1'),
'AnotherClass': ('This class has no separate docstring.', 'self, a, b')}
test_eq(extract_docstrings(sample_code), exp)Rendering docment Tables
DocmentTbl
def DocmentTbl(
obj, verbose:bool=True, returns:bool=True
):Compute the docment table string
DocmentTbl renders parameter documentation as a Markdown table. String annotations can be displayed without resolving their types:
def _f(
a, # description of param a
b=True, # description of param b
c:'Result'=None
) -> int: ...
_dm = DocmentTbl(_f)
assert 'description of param a' in str(_dm)
assert 'Returns' in str(_dm)
assert 'Result' in _dm.params_str
_dm| Type | Default | Details | |
|---|---|---|---|
| a | description of param a | ||
| b | bool | True | description of param b |
| c | Result | None | |
| Returns | int |
Markdown table cells escape pipes and footnote markers. Existing escapes are preserved, and line breaks become <br>.
def compile_pattern(
# Alternatives: a | b
# Escaped form: a \| b
# See ^[syntax]
pattern:str
): return re.compile(pattern)
table = DocmentTbl(compile_pattern)
assert r'Alternatives: a \| b<br>Escaped form: a \| b<br>See \^[syntax]' in str(table)
table| Type | Details | |
|---|---|---|
| pattern | str | Alternatives: a | b Escaped form: a | b See ^[syntax] |
Columns without any values are omitted. This function has no defaults or documented return value, so neither the Default column nor the Returns row appears.
def _f(
a,
b:int, #param b
c:str #param c
):
"Do a thing"
..._dm2 = DocmentTbl(_f)
assert 'Default' not in str(_dm2)
assert 'Returns' not in str(_dm2)
assert 'param b' in str(_dm2)
_dm2| Type | Details | |
|---|---|---|
| a | ||
| b | int | param b |
| c | str | param c |
By default, passing a class to DocmentTbl shows its constructor parameters:
class _Test:
def __init__(
self,
a, # description of param a
b=True, # description of param b
c:str=None
): ...
def foo(
self,
c:int, # description of param c
d=True, # description of param d
): ...DocmentTbl(_Test)| Type | Default | Details | |
|---|---|---|---|
| a | description of param a | ||
| b | bool | True | description of param b |
| c | str | None |
You can also pass a method to be rendered as well:
method_table = DocmentTbl(_Test.foo)
assert 'description of param c' in str(method_table)
assert 'self' not in method_table.dm
method_table| Type | Default | Details | |
|---|---|---|---|
| c | int | description of param c | |
| d | bool | True | description of param d |
DocmentList renders each parameter as a bullet containing its type, default, and description.
listing = DocmentList(_f)
assert 'param b' in str(listing)
listingab:intparam bc:strparam creturn
Signature formatting starts a new line after each documented parameter. Undocumented parameters share a line when they fit.
formatted = _fmt_sig('foo', [('a:int', 'first'), ('b:str', None), ('c', 'third')], ')->int:', 80)
assert 'a:int, # first\n' in formatted
assert 'b:str, c, # third' in formatted
PrettyString(formatted)def foo(
a:int, # first
b:str, c, # third
)->int:
DocmentText keeps formatted parameters separate from their descriptions before assembling the signature.
parameters = DocmentText(_f).params
test_eq(parameters[1], ('b:int', 'param b'))
parameters[('a', None), ('b:int', 'param b'), ('c:str', 'param c')]
DocmentText assembles those parameters into a Python signature with docments beside them.
text = DocmentText(_f)
assert 'b:int, # param b' in str(text)
textdef _f(
a, b:int, # param b
c:str, # param c
):
"Do a thing"def _g(
a, b:int, cccccccccccccccccccc:int, ccccccccdccccccccccc:int, cccccccccccecccccccc:int, cccccccfcccccccccc:int, ccccccccccccgccccc:int, # hi
c:str='foo'
)->str:
"Do a thing"
DocmentText(_g, maxline=80, docstring=False)def _g(
a, b:int, cccccccccccccccccccc:int, ccccccccdccccccccccc:int,
cccccccccccecccccccc:int, cccccccfcccccccccc:int, ccccccccccccgccccc:int, # hi
c:str='foo'
)->str:A partial binds arguments without losing the remaining parameters’ docments.
DocmentText(partial(_g, 1), maxline=80, docstring=False)def partial.__call__(
b:int, cccccccccccccccccccc:int, ccccccccdccccccccccc:int,
cccccccccccecccccccc:int, cccccccfcccccccccc:int, ccccccccccccgccccc:int, # hi
c:str='foo'
)->str:Non-callable objects render as their type and value, rather than pretending to be functions:
test_eq(str(DocmentText(3, docstring=False)), 'int instance: 3')sig2str
def sig2str(
func, maxline:int=110
):Generate function signature with docments as comments
print(sig2str(_d))def _d(
c:int, # First
b:str, # Second
*, a:int=2, z:str='b', # Last
)->int: # Return an int
Signatures retain positional-only / and keyword-only * separators. Varargs keep their */** prefixes, whether annotated or not:
def _va(
a:int, # First value
/, *ids:str, limit:Annotated[int, 'Maximum results']=10, **kw
) -> list[str]: pass
sigtext = str(sig2str(_va))
assert all(s in sigtext for s in ('First value', '/', '*ids:str', 'limit:int=10', 'Maximum results', '**kw', 'list[str]'))
def _vb(a, /, *, b=1): pass
assert ', /, *,' in str(sig2str(_vb))
sig2str(_va)def _va(
a:int, # First value
/, *ids:str, limit:int=10, # Maximum results
**kw
)->list[str]:Documentation For An Object
Render the signature as well as the docments to show complete documentation for an object.
NumPy parameter and return sections are omitted from the remaining prose when their types agree with the signature. Their descriptions will appear beside the parameters instead.
remaining = _docstring(add_np, docments(add_np, full=True))
assert 'Parameters' not in remaining
assert 'The sum of two numbers.' in remaining
PrettyString(remaining)The sum of two numbers.
Used to demonstrate numpy-style docstrings.
ShowDocRenderer
def ShowDocRenderer(
sym, name:str | None=None, title_level:int=3, maxline:int=110
):Show documentation for sym
MarkdownRenderer
def MarkdownRenderer(
sym, name:str | None=None, title_level:int=3, maxline:int=110
):Markdown renderer for show_doc
NumPy-style sections remain part of the full documentation. Parameter and return descriptions appear beside the signature; notes, errors, and examples stay below it.
def bounded_add(a:int, b:int=0) -> int:
"""Add two non-negative numbers.
Parameters
----------
a : int
First operand.
b : int
Second operand.
Returns
-------
int
Their sum.
Notes
-----
Both operands must be non-negative.
Raises
------
ValueError
An operand is negative.
Examples
--------
>>> bounded_add(2, 3)
5
"""
if min(a, b) < 0: raise ValueError('Negative operand')
return a+b
rendered = str(MarkdownRenderer(bounded_add))
assert all(s in rendered for s in ('First operand.', 'Their sum.', 'Both operands', 'ValueError', 'bounded_add(2, 3)'))
assert 'Parameters\n' not in rendered and 'Returns\n' not in rendered
MarkdownRenderer(bounded_add)def bounded_add(
a:int, # First operand.
b:int=0, # Second operand.
)->int: # Their sum.Add two non-negative numbers.
Notes
Both operands must be non-negative.
Raises
ValueError An operand is negative.
Examples
bounded_add(2, 3) 5
If a symbol’s source is unreadable, the renderer shows its signature without docments and warns with the symbol name and error:
def _stale(): pass
_stale.__code__ = _stale.__code__.replace(co_firstlineno=99999)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
r = MarkdownRenderer(_stale)
assert 'docments unavailable' in str(w[0].message)
assert '_stale' in str(r)
r_stale()def _f(
a,
b:callable=print, #param b
c:str='foo' #param c
)->str: # Result of doing it
"Do a thing"
...
MarkdownRenderer(_f)def _f(
a, b:<built-in function callable>=print, # param b
c:str='foo', # param c
)->str: # Result of doing itDo a thing
print(MarkdownRenderer(_f))def _f(
a, b:<built-in function callable>=print, # param b
c:str='foo', # param c
)->str: # Result of doing it"""Do a thing"""
Delegated signatures retain the wrapper’s own docments and the delegated parameters.
def f(a:int=0 # aa
): pass
@delegates(f)
def g(
b:int|str, # bb
**kwargs
): return kwargsMarkdownRenderer(g)def g(
b:int | str, # bb
*, a:int=0, # aa
):MarkdownRenderer(add_async)async def add_async(
a:int, # The first operand
b:int, # This is the second of the operands to the *addition* operator.
# Note that passing a negative value here is the equivalent of the *subtraction* operator.
)->int: # The result is calculated using Python's builtin `+` operator.Add a to b
Some builtins have no inspectable signature. Their original docstring still supplies the calling convention and behavior.
builtin_docs = MarkdownRenderer(next)
test_is(builtin_docs.sig, None)
assert 'StopIteration' in builtin_docs.docs
builtin_docsdef next():next(iterator[, default])
Return the next item from the iterator. If default is given and the iterator is exhausted, it is returned instead of raising StopIteration.
class Foo:
"A callable calculator"
async def __call__(self,
x:int, # Input value
) -> int:
"Calculate asynchronously"
return x
foo = Foo()
rendered = str(MarkdownRenderer(foo))
assert all(s in rendered for s in ('async def', 'Input value', 'Calculate asynchronously'))
MarkdownRenderer(foo)async def Foo.__call__(
x:int, # Input value
)->int:Calculate asynchronously
Fastgit’s Git resolves arbitrary subcommands through __getattr__, including names such as fget. Probing for fget can mistake such an instance for a property. The renderer uses isinstance to identify properties. Here it documents the instance’s own __call__, not a partial returned by __getattr__:
class Bar:
def __call__(self, x):
"Call bar"
return x
def __getattr__(self, nm):
if nm.startswith('_'): raise AttributeError(nm)
return partial(self, nm)
r = MarkdownRenderer(Bar())
assert 'Bar.__call__' in str(r)
rdef Bar.__call__(
x
):Call bar