Follow#

This module provides a decorator follow that allows functions to be “followed” in a call graph, as well as a function graphviz that generates a call graph of the decorated functions. The decorator can be used with an optional label for the wrapped function in the call graph, and the generated graph can be written to a file-like object.

Summary#

follow.follow([label])

Decorator that allows a function to be "followed" in a call graph.

follow.graphviz(buf)

Decorator that generates a call graph of the decorated functions.

follow.loop()

Determines whether there is a loop in the call graph.

Functions#

follow.follow(label=None)#

Decorator that allows a function to be “followed” in a call graph.

Parameters#

labelstr, optional

A label for the wrapped function in the call graph. If not provided, the function name will be used as the label.

Returns#

function

A wrapped version of the input function that can be used to generate a call graph.

Examples#

>>> @follow()
... def add(a, b):
...     return a + b
>>> add(2, 3)
5
>>> @follow(label='Subtract')
... def sub(a, b):
...     return a - b
>>> sub(5, 2)
3
>>> add = follow()(lambda a, b: a + b)
>>> add(2, 3)
5
follow.graphviz(buf)#

Decorator that generates a call graph of the decorated functions.

Parameters#

bufTextIOWrapper

A file-like object that the graph will be written to.

Returns#

None

Examples#

>>> from io import StringIO
>>> clear()
>>> @follow(label='Addition')
... def add(a, b):
...     return a + b
>>> @follow(label='Subtraction')
... def sub(a, b):
...     return a - b
>>> @follow()
... def foo():
...     add(1, 2)
...     sub(4, 3)
>>> foo()
>>> buf = StringIO()
>>> graphviz(buf)
>>> print(buf.getvalue())
digraph {
  0 [label = "Addition"]
  1 [label = "Subtraction"]
  2 [label = "foo"]
  2 -> 0
  2 -> 1
}
follow.loop()#

Determines whether there is a loop in the call graph.

Returns#

bool

True if there is a loop in the call graph, False otherwise.

Examples#

>>> clear()
>>> @follow()
... def func1(x):
...     if x > 0:
...         return func2(x-1)
...     else:
...         return 0
>>> @follow()
... def func2(x):
...     if x > 0:
...         return func1(x-1)
...     else:
...         return 0
>>> func2(42)
0
>>> has_loop = loop()
>>> print(has_loop)
True
>>> clear()
>>> @follow()
... def func1(x):
...     return func2(x-1)
>>> 
>>> @follow()
... def func2(x):
...     pass
>>> func2(42)
>>> loop()
False