According to the Zen of Python, complex is better than complicated, but it requires the code to be flat, and flat code means less nodes to visit during parsing. It also means that the code should be plain, without too much nested statements, well structured and small. Cyclomatic Complexity is a metric that allows you to know how many execution paths, or nodes are visited during the program execution. I was searching a program or module that allows me to know the Cyclomatic Complexity of my Python programs, even if have pylint, pychecker, pep8 and pyflakes to check my code, a well organized project, that is strict on its governance, maintains a well structured Continuous Integration tools with this kind of analysis enabled.
I have found a nice program called pygenie, which allows me to calculate the Cyclomatic Complexity of Python programs, delivering a detailed measurement of each root node, like functions, classes and methods, allowing me to know where I’m failing on the required metrics for well structured programs. The example output is as follows:
File: /opt/bin/pycheckers.py Type Name Complexity -------------------------------------- M LintRunner.run 15 X /opt/bin/pycheckers.py 8
The M flag, the method run of the LintRunner class has a complexity value of 15, and the X for the pychckers.py file, means that the overall script on the __main__ block, has a complexity value of 8.
Cyclomatic Complexity | |
---|---|
Level | Risk |
1 to 10 | A simple program, without much risk |
11 to 20 | More complex, moderate risk |
21 to 50 | Complex, high risk program |
up to 50 | Unstable program (very high risk) |
The densest functions and method are those which are holding the higher risk value of any application, and makes harder to control the execution path. As I am relatively uncomfortable with many tools that are provided as is, I have modified pygenie to extract the parsed AST, which is compiled from the built-in Python parser provided on the compiler Python package. That was made with the intention to visualize the complete Abstract Syntax Tree providing a visualization similar to the Control Flow Graph, and made on top of the pydot module, which provides an interface to the GraphViz software package. I think that the result is amazing, here are some samples of it.
That custom logging module only has three functions, few imports and one constant, and its AST looks huge. Let’s look another AST for another module, which contains decorator definitions for Django.
The code to build the AST is quite simple, it just runs a recursive function through the parsed AST, collecting names and adding nodes under the GraphViz draw, using the Dot functions that provide the pydot package. It’s very nice to have an overview of the AST, so we can extract the CFG from it. Probably in the future I will provide this tool, plus the CFG generation from the Python code.
def run_graph(ccv, rootg, current_node, inline): """ Extracts the AST Visualization """ lineno = get_lineno(ccv, inline) node_desc = get_node_desc(ccv, lineno) if rootg == None: rootg = pd.Dot(node_desc, graph_type='digraph') cn = pd.Node(node_desc, label=node_desc) rootg.add_node(cn) else: node = pd.Node(node_desc, label=node_desc) rootg.add_node(node) rootg.add_edge(pd.Edge(current_node, node)) cn = node for nd in ccv.getChildNodes(): run_graph(nd, rootg, cn, lineno) return rootg
I hope that you can integrate a similar function in your complexity calculation tasks :)