CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-radon

Code Metrics in Python - comprehensive tool for computing various software metrics

Pending
Overview
Eval results
Files

halstead.mddocs/

Halstead 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.

Capabilities

Main Analysis Functions

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.
    """

Halstead Data Types

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
])

Halstead Metrics Explained

The Halstead metrics are based on counting operators and operands in source code:

Basic Counts

  • h1: Number of distinct operators (e.g., +, -, if, def, return)
  • h2: Number of distinct operands (e.g., variable names, literals, function names)
  • N1: Total occurrences of all operators
  • N2: Total occurrences of all operands

Derived Metrics

  • Vocabulary (h): h1 + h2 - The total number of unique symbols
  • Length (N): N1 + N2 - The total number of symbols
  • Calculated Length: h1 × log₂(h1) + h2 × log₂(h2) - Theoretical minimum length
  • Volume (V): N × log₂(h) - Number of bits needed to encode the program
  • Difficulty (D): (h1 ÷ 2) × (N2 ÷ h2) - How difficult the program is to understand
  • Effort (E): D × V - Mental effort required to understand the program
  • Time (T): E ÷ 18 - Estimated time to understand (in seconds)
  • Bugs (B): V ÷ 3000 - Estimated number of delivered bugs

Usage Examples

Basic Halstead Analysis

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}")

Comparing Code Complexity

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}")

AST-Level Analysis

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")

Custom Visitor Processing

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]}")

Understanding Halstead Metrics

Interpreting Volume

  • Low volume (< 100): Simple functions or scripts
  • Medium volume (100-1000): Typical application functions
  • High volume (> 1000): Complex algorithms or large functions

Interpreting Difficulty

  • Low difficulty (< 10): Easy to understand and maintain
  • Medium difficulty (10-30): Moderate complexity
  • High difficulty (> 30): Challenging to understand

Interpreting Effort

  • Low effort (< 1000): Minimal mental effort required
  • Medium effort (1000-10000): Moderate mental effort
  • High effort (> 10000): Significant mental effort required

Integration with Other Metrics

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}")

Error Handling

Halstead analysis handles various edge cases:

  • Empty code: Returns HalsteadReport with zero values
  • Invalid syntax: AST parsing errors are propagated
  • Division by zero: Returns appropriate defaults when h2=0 for difficulty calculation
  • Mathematical edge cases: Handles log₂(0) by treating empty vocabulary as 1

Integration with CLI

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.py

Install with Tessl CLI

npx tessl i tessl/pypi-radon

docs

cli.md

complexity.md

halstead.md

index.md

maintainability.md

raw-metrics.md

visitors.md

tile.json