0
# Halstead Metrics
1
2
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.
3
4
## Capabilities
5
6
### Main Analysis Functions
7
8
Primary functions for computing Halstead metrics from source code or AST nodes.
9
10
```python { .api }
11
def h_visit(code):
12
"""
13
Compile code into AST and compute Halstead metrics.
14
15
Analyzes Python source code to extract operators and operands,
16
then calculates comprehensive Halstead software science metrics.
17
18
Parameters:
19
- code (str): Python source code to analyze
20
21
Returns:
22
Halstead: Named tuple with 'total' (file-level metrics) and 'functions' (per-function metrics)
23
"""
24
25
def h_visit_ast(ast_node):
26
"""
27
Visit AST node using HalsteadVisitor to compute metrics.
28
29
Directly analyzes an AST node without parsing source code.
30
Used internally by h_visit() and for custom AST processing.
31
32
Parameters:
33
- ast_node: Python AST node to analyze
34
35
Returns:
36
Halstead: Named tuple with total and per-function metrics
37
"""
38
39
def halstead_visitor_report(visitor):
40
"""
41
Create HalsteadReport from a configured HalsteadVisitor.
42
43
Converts visitor's collected operator/operand data into
44
a structured report with computed metrics.
45
46
Parameters:
47
- visitor (HalsteadVisitor): Visitor instance after AST traversal
48
49
Returns:
50
HalsteadReport: Complete metrics including h1, h2, N1, N2, volume, difficulty, etc.
51
"""
52
```
53
54
### Halstead Data Types
55
56
Named tuples containing Halstead metrics and analysis results.
57
58
```python { .api }
59
# Individual Halstead metrics report
60
HalsteadReport = namedtuple('HalsteadReport', [
61
'h1', # Number of distinct operators
62
'h2', # Number of distinct operands
63
'N1', # Total number of operators
64
'N2', # Total number of operands
65
'vocabulary', # h = h1 + h2 (vocabulary)
66
'length', # N = N1 + N2 (program length)
67
'calculated_length', # h1 * log2(h1) + h2 * log2(h2)
68
'volume', # V = N * log2(h) (program volume)
69
'difficulty', # D = (h1 / 2) * (N2 / h2) (programming difficulty)
70
'effort', # E = D * V (programming effort)
71
'time', # T = E / 18 (programming time in seconds)
72
'bugs' # B = V / 3000 (estimated delivered bugs)
73
])
74
75
# Combined results for file and functions
76
Halstead = namedtuple('Halstead', [
77
'total', # HalsteadReport for the entire file
78
'functions' # List of HalsteadReport objects for each function
79
])
80
```
81
82
## Halstead Metrics Explained
83
84
The Halstead metrics are based on counting operators and operands in source code:
85
86
### Basic Counts
87
- **h1**: Number of distinct operators (e.g., +, -, if, def, return)
88
- **h2**: Number of distinct operands (e.g., variable names, literals, function names)
89
- **N1**: Total occurrences of all operators
90
- **N2**: Total occurrences of all operands
91
92
### Derived Metrics
93
- **Vocabulary (h)**: h1 + h2 - The total number of unique symbols
94
- **Length (N)**: N1 + N2 - The total number of symbols
95
- **Calculated Length**: h1 × log₂(h1) + h2 × log₂(h2) - Theoretical minimum length
96
- **Volume (V)**: N × log₂(h) - Number of bits needed to encode the program
97
- **Difficulty (D)**: (h1 ÷ 2) × (N2 ÷ h2) - How difficult the program is to understand
98
- **Effort (E)**: D × V - Mental effort required to understand the program
99
- **Time (T)**: E ÷ 18 - Estimated time to understand (in seconds)
100
- **Bugs (B)**: V ÷ 3000 - Estimated number of delivered bugs
101
102
## Usage Examples
103
104
### Basic Halstead Analysis
105
106
```python
107
from radon.metrics import h_visit
108
109
code = '''
110
def factorial(n):
111
if n <= 1:
112
return 1
113
else:
114
return n * factorial(n - 1)
115
116
def fibonacci(n):
117
if n <= 1:
118
return n
119
return fibonacci(n - 1) + fibonacci(n - 2)
120
'''
121
122
# Analyze Halstead metrics
123
result = h_visit(code)
124
125
# File-level metrics
126
total = result.total
127
print(f"File Halstead Metrics:")
128
print(f" Distinct operators (h1): {total.h1}")
129
print(f" Distinct operands (h2): {total.h2}")
130
print(f" Total operators (N1): {total.N1}")
131
print(f" Total operands (N2): {total.N2}")
132
print(f" Vocabulary: {total.vocabulary}")
133
print(f" Length: {total.length}")
134
print(f" Volume: {total.volume:.2f}")
135
print(f" Difficulty: {total.difficulty:.2f}")
136
print(f" Effort: {total.effort:.2f}")
137
print(f" Time: {total.time:.2f} seconds")
138
print(f" Estimated bugs: {total.bugs:.4f}")
139
140
# Per-function metrics
141
print(f"\nFunction-level metrics:")
142
for i, func_metrics in enumerate(result.functions):
143
print(f" Function {i+1}:")
144
print(f" Volume: {func_metrics.volume:.2f}")
145
print(f" Difficulty: {func_metrics.difficulty:.2f}")
146
print(f" Effort: {func_metrics.effort:.2f}")
147
```
148
149
### Comparing Code Complexity
150
151
```python
152
from radon.metrics import h_visit
153
154
# Simple code
155
simple_code = '''
156
def add(a, b):
157
return a + b
158
'''
159
160
# Complex code
161
complex_code = '''
162
def complex_calculator(operation, *args, **kwargs):
163
operations = {
164
'add': lambda x, y: x + y,
165
'subtract': lambda x, y: x - y,
166
'multiply': lambda x, y: x * y,
167
'divide': lambda x, y: x / y if y != 0 else None
168
}
169
170
if operation not in operations:
171
raise ValueError(f"Unknown operation: {operation}")
172
173
if len(args) < 2:
174
raise ValueError("Need at least 2 arguments")
175
176
result = args[0]
177
for arg in args[1:]:
178
result = operations[operation](result, arg)
179
180
return result
181
'''
182
183
simple_result = h_visit(simple_code)
184
complex_result = h_visit(complex_code)
185
186
print("Simple function:")
187
print(f" Volume: {simple_result.total.volume:.2f}")
188
print(f" Difficulty: {simple_result.total.difficulty:.2f}")
189
190
print("Complex function:")
191
print(f" Volume: {complex_result.total.volume:.2f}")
192
print(f" Difficulty: {complex_result.total.difficulty:.2f}")
193
```
194
195
### AST-Level Analysis
196
197
```python
198
import ast
199
from radon.metrics import h_visit_ast
200
201
code = '''
202
def greet(name, greeting="Hello"):
203
message = f"{greeting}, {name}!"
204
print(message)
205
return message
206
'''
207
208
# Parse to AST manually
209
ast_tree = ast.parse(code)
210
211
# Analyze using AST directly
212
result = h_visit_ast(ast_tree)
213
214
print(f"AST Analysis Results:")
215
print(f" Vocabulary: {result.total.vocabulary}")
216
print(f" Volume: {result.total.volume:.2f}")
217
print(f" Estimated development time: {result.total.time:.1f} seconds")
218
```
219
220
### Custom Visitor Processing
221
222
```python
223
from radon.metrics import halstead_visitor_report
224
from radon.visitors import HalsteadVisitor
225
import ast
226
227
code = '''
228
def process_data(data_list):
229
results = []
230
for item in data_list:
231
if isinstance(item, (int, float)):
232
results.append(item * 2)
233
elif isinstance(item, str):
234
results.append(item.upper())
235
return results
236
'''
237
238
# Create and use visitor manually
239
ast_tree = ast.parse(code)
240
visitor = HalsteadVisitor()
241
visitor.visit(ast_tree)
242
243
# Generate report from visitor
244
report = halstead_visitor_report(visitor)
245
246
print(f"Custom Analysis:")
247
print(f" Operators found: {len(visitor.distinct_operators)}")
248
print(f" Operands found: {len(visitor.distinct_operands)}")
249
print(f" Volume: {report.volume:.2f}")
250
print(f" Difficulty: {report.difficulty:.2f}")
251
252
# Examine specific operators and operands
253
print(f" Sample operators: {list(visitor.distinct_operators)[:5]}")
254
print(f" Sample operands: {list(visitor.distinct_operands)[:5]}")
255
```
256
257
## Understanding Halstead Metrics
258
259
### Interpreting Volume
260
- **Low volume (< 100)**: Simple functions or scripts
261
- **Medium volume (100-1000)**: Typical application functions
262
- **High volume (> 1000)**: Complex algorithms or large functions
263
264
### Interpreting Difficulty
265
- **Low difficulty (< 10)**: Easy to understand and maintain
266
- **Medium difficulty (10-30)**: Moderate complexity
267
- **High difficulty (> 30)**: Challenging to understand
268
269
### Interpreting Effort
270
- **Low effort (< 1000)**: Minimal mental effort required
271
- **Medium effort (1000-10000)**: Moderate mental effort
272
- **High effort (> 10000)**: Significant mental effort required
273
274
## Integration with Other Metrics
275
276
Halstead metrics work well in combination with other radon analyses:
277
278
```python
279
from radon.metrics import h_visit, mi_compute
280
from radon.complexity import cc_visit, average_complexity
281
from radon.raw import analyze
282
283
code = '''
284
def complex_algorithm(data, threshold=0.5):
285
results = []
286
for i, item in enumerate(data):
287
if isinstance(item, dict):
288
score = sum(v for v in item.values() if isinstance(v, (int, float)))
289
if score > threshold:
290
results.append({'index': i, 'score': score, 'data': item})
291
elif isinstance(item, (list, tuple)):
292
nested_score = sum(x for x in item if isinstance(x, (int, float)))
293
if nested_score > threshold:
294
results.append({'index': i, 'score': nested_score, 'data': item})
295
return sorted(results, key=lambda x: x['score'], reverse=True)
296
'''
297
298
# Get all metrics
299
halstead = h_visit(code)
300
complexity_blocks = cc_visit(code)
301
raw_metrics = analyze(code)
302
303
# Extract values for MI calculation
304
halstead_volume = halstead.total.volume
305
avg_complexity = average_complexity(complexity_blocks)
306
sloc = raw_metrics.sloc
307
comments = raw_metrics.comments
308
309
# Calculate maintainability index
310
mi_score = mi_compute(halstead_volume, avg_complexity, sloc, comments)
311
312
print(f"Comprehensive Analysis:")
313
print(f" Halstead Volume: {halstead_volume:.2f}")
314
print(f" Cyclomatic Complexity: {avg_complexity:.2f}")
315
print(f" Source Lines: {sloc}")
316
print(f" Maintainability Index: {mi_score:.2f}")
317
```
318
319
## Error Handling
320
321
Halstead analysis handles various edge cases:
322
323
- **Empty code**: Returns HalsteadReport with zero values
324
- **Invalid syntax**: AST parsing errors are propagated
325
- **Division by zero**: Returns appropriate defaults when h2=0 for difficulty calculation
326
- **Mathematical edge cases**: Handles log₂(0) by treating empty vocabulary as 1
327
328
## Integration with CLI
329
330
Halstead metrics integrate with radon's command-line interface:
331
332
```bash
333
# Command-line equivalent of h_visit()
334
radon hal path/to/code.py
335
336
# Per-function analysis
337
radon hal --functions path/to/code.py
338
339
# JSON output for programmatic processing
340
radon hal --json path/to/code.py
341
```