Code Metrics in Python - comprehensive tool for computing various software metrics
—
McCabe's cyclomatic complexity analysis for measuring code complexity and maintainability. Provides complexity scoring with A-F ranking system, detailed block-level analysis for functions, methods, and classes, and utilities for processing and sorting results.
Main functions for analyzing cyclomatic complexity from source code or AST nodes.
def cc_visit(code, **kwargs):
"""
Visit the given code with ComplexityVisitor.
Parameters:
- code (str): Python source code to analyze
- **kwargs: Keyword arguments passed to ComplexityVisitor
- to_method (bool): Whether to treat top-level functions as methods
- classname (str): Class name for method analysis
- off (bool): Whether to include decorator complexity
- no_assert (bool): Whether to exclude assert statements from complexity
Returns:
list: List of Function and Class objects representing code blocks
"""
def cc_visit_ast(ast_node, **kwargs):
"""
Visit the AST node with ComplexityVisitor.
Parameters:
- ast_node: Python AST node to analyze
- **kwargs: Keyword arguments passed to ComplexityVisitor
Returns:
list: List of Function and Class objects representing code blocks
"""Functions for ranking complexity scores and processing analysis results.
def cc_rank(cc):
"""
Rank the complexity score from A to F.
Ranking system:
- A (1-5): Low risk - simple block
- B (6-10): Low risk - well structured and stable block
- C (11-20): Moderate risk - slightly complex block
- D (21-30): More than moderate risk - more complex block
- E (31-40): High risk - complex block, alarming
- F (41+): Very high risk - error-prone, unstable block
Parameters:
- cc (int): Complexity score (must be non-negative)
Returns:
str: Single letter grade (A-F)
Raises:
ValueError: If complexity is negative
"""
def average_complexity(blocks):
"""
Compute the average cyclomatic complexity from blocks.
Parameters:
- blocks (list): List of Function or Class objects
Returns:
float: Average complexity score, or 0 if blocks is empty
"""
def sorted_results(blocks, order=SCORE):
"""
Sort blocks by complexity with specified ordering.
Parameters:
- blocks (list): List of Function or Class objects
- order (function): Sorting function, one of:
- SCORE: Sort by complexity score (descending) - default
- LINES: Sort by line number (ascending)
- ALPHA: Sort alphabetically by name (ascending)
Returns:
list: Sorted list of blocks
"""
def add_inner_blocks(blocks):
"""
Process blocks by adding closures and inner classes as top-level blocks.
Flattens nested functions and inner classes into the main block list.
Parameters:
- blocks (list): List of Function or Class objects
Returns:
list: Expanded list with nested blocks promoted to top-level
"""Predefined sorting functions for use with sorted_results().
# Sort by complexity score (descending)
SCORE = lambda block: -GET_COMPLEXITY(block)
# Sort by line number (ascending)
LINES = lambda block: block.lineno
# Sort alphabetically by name (ascending)
ALPHA = lambda block: block.namefrom radon.complexity import cc_visit, cc_rank
code = '''
def simple_function():
return True
def complex_function(x, y, z):
if x > 0:
if y > 0:
if z > 0:
return x + y + z
else:
return x + y
else:
return x
else:
return 0
'''
# Analyze complexity
blocks = cc_visit(code)
for block in blocks:
rank = cc_rank(block.complexity)
print(f"{block.name}: {block.complexity} ({rank})")
# Output:
# simple_function: 1 (A)
# complex_function: 4 (A)from radon.complexity import cc_visit, sorted_results, average_complexity, SCORE, LINES, ALPHA
code = '''
class Calculator:
def add(self, a, b):
return a + b
def complex_divide(self, a, b):
if b == 0:
raise ValueError("Division by zero")
elif isinstance(a, str) or isinstance(b, str):
raise TypeError("String division not supported")
else:
return a / b
def utility_function():
pass
'''
blocks = cc_visit(code)
# Sort by complexity (default)
by_complexity = sorted_results(blocks, SCORE)
print("By complexity:")
for block in by_complexity:
print(f" {block.name}: {block.complexity}")
# Sort by line number
by_lines = sorted_results(blocks, LINES)
print("By line number:")
for block in by_lines:
print(f" Line {block.lineno}: {block.name}")
# Sort alphabetically
by_name = sorted_results(blocks, ALPHA)
print("By name:")
for block in by_name:
print(f" {block.name}: {block.complexity}")
# Calculate average complexity
avg = average_complexity(blocks)
print(f"Average complexity: {avg:.2f}")from radon.complexity import cc_visit
# Analyze as standalone functions
blocks_as_functions = cc_visit(class_code, to_method=False)
# Analyze as methods within a class context
blocks_as_methods = cc_visit(class_code, to_method=True, classname="MyClass")
for block in blocks_as_methods:
if block.is_method:
print(f"Method {block.fullname}: {block.complexity}")
else:
print(f"Function {block.name}: {block.complexity}")from radon.complexity import cc_visit, add_inner_blocks
code_with_nested = '''
class OuterClass:
def method(self):
def inner_function():
return True
return inner_function()
class InnerClass:
def inner_method(self):
pass
'''
# Get blocks including nested structures
blocks = cc_visit(code_with_nested)
# Flatten nested blocks to top level
all_blocks = add_inner_blocks(blocks)
print("All blocks (including nested):")
for block in all_blocks:
print(f" {block.name}: {block.complexity}")The complexity analysis functions handle various error conditions:
cc_rank() raises ValueError for negative complexity valuesaverage_complexity() returns 0 for empty listsThe complexity module integrates with radon's command-line interface:
# Command-line equivalent of cc_visit()
radon cc path/to/code.py
# With ranking and complexity display
radon cc --show-complexity --min A --max F path/to/code.py
# JSON output for programmatic processing
radon cc --json path/to/code.pyInstall with Tessl CLI
npx tessl i tessl/pypi-radon