There are many different tools that you can install to help you be a better Python programmer. For example, you might install pytest so that you can do unit tests of your code. Or you might install Ruff, a super fast Python linter. The focus of this article is on another tool called Radon that helps you compute code metrics.
You can use Radon to help you find complex code in your code base. This is known as Cyclomatic Complexity or McCabe’s Complexity. According to Radon’s documentation:
“Cyclomatic Complexity corresponds to the number of decisions a block of code contains plus 1. This number (also called McCabe number) is equal to the number of linearly independent paths through the code. This number can be used as a guide when testing conditional logic in blocks.”
For example, if the number equals three, you will probably need to write at least three unit tests to have complete code coverage. Not only is this useful for figuring out how many tests to write, the cyclomatic complexity can tell you when it is time to refactor your code. You can read the full details of how Radon calculates complexity in their documentation.
Experienced developers are often able to know when to refactor from their own experience, but newer engineers may need the help that a tool like this provides.
You can also use a tool like Radon in your CI/CD system to prevent developers from merging in overly complex code.
You can install Radon using pip. Here is an example:
python -m pip install radon
Now, let’s learn how to use Radon on your code!
Basic Radon Usage
You can call the radon application on the command line. Radon accepts multiple flags to control what kinds of output you receive.
Here are the four commands that radon currently can use:
- cc: compute Cyclomatic Complexity
- raw: compute raw metrics
- mi: compute Maintainability Index
- hal: compute Halstead complexity metrics
Let’s try running radon against the popular Black package (Black is a popular code formatted for Python).
Here is the command to run:
PS C:\Users\Mike\AppData\Local\Programs\Python\Python311\Lib\site-packages\black> radon cc . -a -nc brackets.py F 225:0 is_split_before_delimiter - F M 70:4 BracketTracker.mark - C comments.py F 140:0 convert_one_fmt_off_pair - D F 208:0 generate_ignored_nodes - C F 253:0 _generate_ignored_nodes_from_fmt_skip - C concurrency.py F 120:0 schedule_formatting - C files.py F 309:0 gen_python_files - C F 46:0 find_project_root - C linegen.py F 1133:0 normalize_invisible_parens - D F 747:0 _maybe_split_omitting_optional_parens - D F 1453:0 generate_trailers_to_omit - D F 879:0 bracket_split_build_line - D M 396:4 LineGenerator.visit_STRING - C F 509:0 transform_line - C F 997:0 delimiter_split - C F 1529:0 run_transformer - C F 1355:0 maybe_make_parens_invisible_in_atom - C F 632:0 left_hand_split - C M 289:4 LineGenerator.visit_simple_stmt - C F 699:0 _first_right_hand_split - C F 1313:0 remove_with_parens - C lines.py M 569:4 EmptyLineTracker._maybe_empty_lines - E M 646:4 EmptyLineTracker._maybe_empty_lines_for_class_or_def - E F 755:0 is_line_short_enough - D C 514:0 EmptyLineTracker - D F 882:0 can_omit_invisible_parens - C M 300:4 Line.has_magic_trailing_comma - C F 846:0 can_be_split - C M 62:4 Line.append - C M 529:4 EmptyLineTracker.maybe_empty_lines - C M 228:4 Line.contains_uncollapsable_type_comments - C M 362:4 Line.append_comment - C nodes.py F 174:0 whitespace - F F 616:0 is_simple_decorator_trailer - C F 573:0 is_one_sequence_between - C parsing.py F 164:0 stringify_ast - C F 57:0 lib2to3_parse - C strings.py F 173:0 normalize_string_quotes - C trans.py M 792:4 StringParenStripper.do_match - D M 1388:4 StringSplitter.do_transform - D M 2070:4 StringParenWrapper.do_transform - D M 1064:4 BaseStringSplitter._get_max_string_length - D M 1334:4 StringSplitter.do_splitter_match - C M 686:4 StringMerger._validate_msg - C M 542:4 StringMerger._merge_one_string_group - C F 1219:0 iter_fexpr_spans - C C 772:0 StringParenStripper - C M 378:4 StringMerger.do_match - C C 1801:0 StringParenWrapper - C M 1859:4 StringParenWrapper.do_splitter_match - C F 84:0 hug_power_op - C C 358:0 StringMerger - C M 1174:4 BaseStringSplitter._prefer_paren_wrap_match - C M 1985:4 StringParenWrapper._assign_match - C M 2032:4 StringParenWrapper._dict_or_lambda_match - C __init__.py F 1156:0 get_features_used - F F 443:0 main - E F 616:0 get_sources - D F 749:0 reformat_one - C F 121:0 read_pyproject_toml - C F 1094:0 _format_str_once - C F 800:0 format_file_in_place - C 62 blocks (classes, functions, methods) analyzed. Average complexity: C (19.741935483870968)
In this example, you asked radon to give you the Cyclomatic Complexity (cc) of the Black package. You also tacked on the -a or average flag and the -n flag, which lets you set the minimum complexity rank to display. The default is “a”, but in this example, you set the minimum to “c”, meaning it will show ranks C to F.
At this point, you can go through the code and start looking at the functions, methods, and classes to see what a C-ranked portion of code looks like compared with an F-ranked one. Give it a try and you’ll soon learn how to use radon to discover how complex your code is.
There are more command line options than what is shown here. Check out the full listing of additional flag in radon’s documentation.
You can also have radon measure the maintainability of your code by using the mi command. Here’s an example:
PS C:\Users\Mike\AppData\Local\Programs\Python\Python311\Lib\site-packages\black> radon mi . brackets.py - A cache.py - A comments.py - A concurrency.py - A const.py - A debug.py - A files.py - A handle_ipynb_magics.py - A linegen.py - C lines.py - C mode.py - A nodes.py - C numerics.py - A output.py - A parsing.py - A report.py - A rusty.py - A strings.py - A trans.py - C _width_table.py - A __init__.py - C __main__.py - A
The results of the mi command are similar to the cc command as radon will once again use letter ranks on your files to help you grade your code’s maintainability.
Radon can also calculate some raw metrics about your code by using the raw command. Here are the metrics that this command will calculate for you:
- LOC: the total number of lines of code
- LLOC: the number of logical lines of code
- SLOC: the number of source lines of code – not necessarily corresponding to the LLOC [Wikipedia]
- comments: the number of Python comment lines (i.e. only single-line comments
- multi: the number of lines representing multi-line strings
- blank: the number of blank lines (or whitespace-only ones)
Let’s try running the raw command against Black:
PS C:\Users\wheifrd\AppData\Local\Programs\Python\Python311\Lib\site-packages\black> radon raw . brackets.py LOC: 375 LLOC: 197 SLOC: 253 Comments: 4 Single comments: 11 Multi: 47 Blank: 64 - Comment Stats (C % L): 1% (C % S): 2% (C + M % L): 14% cache.py LOC: 97 LLOC: 59 SLOC: 54 Comments: 2 Single comments: 5 Multi: 14 Blank: 24 - Comment Stats (C % L): 2% (C % S): 4% (C + M % L): 16% comments.py LOC: 329 LLOC: 202 SLOC: 212 Comments: 33 Single comments: 29 Multi: 42 Blank: 46 - Comment Stats (C % L): 10% (C % S): 16% (C + M % L): 23%
The example above has been truncated since there is a LOT of output. But this snippet gives you a good idea of what to expect when you run the raw command against your code.
Radon is a great tool to add to your toolbox. You can use it to help you figure out which parts of your source code are getting too complex. You can also use Radon in your CI/CD infrastructure to prevent developers from checking in overly complex code. You might use this for your own open-source or commercial projects at your employer.
Give it a try and see if you like Radon!