Code Metrics in Python - comprehensive tool for computing various software metrics
—
Software complexity measurement based on operators and operands analysis using Halstead's software science metrics. Calculates vocabulary, length, volume, difficulty, effort, time estimates, and bug predictions to quantitatively assess code complexity and programming difficulty.
Primary functions for computing Halstead metrics from source code or AST nodes.
def h_visit(code):
"""
Compile code into AST and compute Halstead metrics.
Analyzes Python source code to extract operators and operands,
then calculates comprehensive Halstead software science metrics.
Parameters:
- code (str): Python source code to analyze
Returns:
Halstead: Named tuple with 'total' (file-level metrics) and 'functions' (per-function metrics)
"""
def h_visit_ast(ast_node):
"""
Visit AST node using HalsteadVisitor to compute metrics.
Directly analyzes an AST node without parsing source code.
Used internally by h_visit() and for custom AST processing.
Parameters:
- ast_node: Python AST node to analyze
Returns:
Halstead: Named tuple with total and per-function metrics
"""
def halstead_visitor_report(visitor):
"""
Create HalsteadReport from a configured HalsteadVisitor.
Converts visitor's collected operator/operand data into
a structured report with computed metrics.
Parameters:
- visitor (HalsteadVisitor): Visitor instance after AST traversal
Returns:
HalsteadReport: Complete metrics including h1, h2, N1, N2, volume, difficulty, etc.
"""Named tuples containing Halstead metrics and analysis results.
# Individual Halstead metrics report
HalsteadReport = namedtuple('HalsteadReport', [
'h1', # Number of distinct operators
'h2', # Number of distinct operands
'N1', # Total number of operators
'N2', # Total number of operands
'vocabulary', # h = h1 + h2 (vocabulary)
'length', # N = N1 + N2 (program length)
'calculated_length', # h1 * log2(h1) + h2 * log2(h2)
'volume', # V = N * log2(h) (program volume)
'difficulty', # D = (h1 / 2) * (N2 / h2) (programming difficulty)
'effort', # E = D * V (programming effort)
'time', # T = E / 18 (programming time in seconds)
'bugs' # B = V / 3000 (estimated delivered bugs)
])
# Combined results for file and functions
Halstead = namedtuple('Halstead', [
'total', # HalsteadReport for the entire file
'functions' # List of HalsteadReport objects for each function
])The Halstead metrics are based on counting operators and operands in source code:
from radon.metrics import h_visit
code = '''
def factorial(n):
if n <= 1:
return 1
else:
return n * factorial(n - 1)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
'''
# Analyze Halstead metrics
result = h_visit(code)
# File-level metrics
total = result.total
print(f"File Halstead Metrics:")
print(f" Distinct operators (h1): {total.h1}")
print(f" Distinct operands (h2): {total.h2}")
print(f" Total operators (N1): {total.N1}")
print(f" Total operands (N2): {total.N2}")
print(f" Vocabulary: {total.vocabulary}")
print(f" Length: {total.length}")
print(f" Volume: {total.volume:.2f}")
print(f" Difficulty: {total.difficulty:.2f}")
print(f" Effort: {total.effort:.2f}")
print(f" Time: {total.time:.2f} seconds")
print(f" Estimated bugs: {total.bugs:.4f}")
# Per-function metrics
print(f"\nFunction-level metrics:")
for i, func_metrics in enumerate(result.functions):
print(f" Function {i+1}:")
print(f" Volume: {func_metrics.volume:.2f}")
print(f" Difficulty: {func_metrics.difficulty:.2f}")
print(f" Effort: {func_metrics.effort:.2f}")from radon.metrics import h_visit
# Simple code
simple_code = '''
def add(a, b):
return a + b
'''
# Complex code
complex_code = '''
def complex_calculator(operation, *args, **kwargs):
operations = {
'add': lambda x, y: x + y,
'subtract': lambda x, y: x - y,
'multiply': lambda x, y: x * y,
'divide': lambda x, y: x / y if y != 0 else None
}
if operation not in operations:
raise ValueError(f"Unknown operation: {operation}")
if len(args) < 2:
raise ValueError("Need at least 2 arguments")
result = args[0]
for arg in args[1:]:
result = operations[operation](result, arg)
return result
'''
simple_result = h_visit(simple_code)
complex_result = h_visit(complex_code)
print("Simple function:")
print(f" Volume: {simple_result.total.volume:.2f}")
print(f" Difficulty: {simple_result.total.difficulty:.2f}")
print("Complex function:")
print(f" Volume: {complex_result.total.volume:.2f}")
print(f" Difficulty: {complex_result.total.difficulty:.2f}")import ast
from radon.metrics import h_visit_ast
code = '''
def greet(name, greeting="Hello"):
message = f"{greeting}, {name}!"
print(message)
return message
'''
# Parse to AST manually
ast_tree = ast.parse(code)
# Analyze using AST directly
result = h_visit_ast(ast_tree)
print(f"AST Analysis Results:")
print(f" Vocabulary: {result.total.vocabulary}")
print(f" Volume: {result.total.volume:.2f}")
print(f" Estimated development time: {result.total.time:.1f} seconds")from radon.metrics import halstead_visitor_report
from radon.visitors import HalsteadVisitor
import ast
code = '''
def process_data(data_list):
results = []
for item in data_list:
if isinstance(item, (int, float)):
results.append(item * 2)
elif isinstance(item, str):
results.append(item.upper())
return results
'''
# Create and use visitor manually
ast_tree = ast.parse(code)
visitor = HalsteadVisitor()
visitor.visit(ast_tree)
# Generate report from visitor
report = halstead_visitor_report(visitor)
print(f"Custom Analysis:")
print(f" Operators found: {len(visitor.distinct_operators)}")
print(f" Operands found: {len(visitor.distinct_operands)}")
print(f" Volume: {report.volume:.2f}")
print(f" Difficulty: {report.difficulty:.2f}")
# Examine specific operators and operands
print(f" Sample operators: {list(visitor.distinct_operators)[:5]}")
print(f" Sample operands: {list(visitor.distinct_operands)[:5]}")Halstead metrics work well in combination with other radon analyses:
from radon.metrics import h_visit, mi_compute
from radon.complexity import cc_visit, average_complexity
from radon.raw import analyze
code = '''
def complex_algorithm(data, threshold=0.5):
results = []
for i, item in enumerate(data):
if isinstance(item, dict):
score = sum(v for v in item.values() if isinstance(v, (int, float)))
if score > threshold:
results.append({'index': i, 'score': score, 'data': item})
elif isinstance(item, (list, tuple)):
nested_score = sum(x for x in item if isinstance(x, (int, float)))
if nested_score > threshold:
results.append({'index': i, 'score': nested_score, 'data': item})
return sorted(results, key=lambda x: x['score'], reverse=True)
'''
# Get all metrics
halstead = h_visit(code)
complexity_blocks = cc_visit(code)
raw_metrics = analyze(code)
# Extract values for MI calculation
halstead_volume = halstead.total.volume
avg_complexity = average_complexity(complexity_blocks)
sloc = raw_metrics.sloc
comments = raw_metrics.comments
# Calculate maintainability index
mi_score = mi_compute(halstead_volume, avg_complexity, sloc, comments)
print(f"Comprehensive Analysis:")
print(f" Halstead Volume: {halstead_volume:.2f}")
print(f" Cyclomatic Complexity: {avg_complexity:.2f}")
print(f" Source Lines: {sloc}")
print(f" Maintainability Index: {mi_score:.2f}")Halstead analysis handles various edge cases:
Halstead metrics integrate with radon's command-line interface:
# Command-line equivalent of h_visit()
radon hal path/to/code.py
# Per-function analysis
radon hal --functions path/to/code.py
# JSON output for programmatic processing
radon hal --json path/to/code.pyInstall with Tessl CLI
npx tessl i tessl/pypi-radon