0
# Cyclomatic Complexity Analysis
1
2
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.
3
4
## Capabilities
5
6
### Code Analysis Functions
7
8
Main functions for analyzing cyclomatic complexity from source code or AST nodes.
9
10
```python { .api }
11
def cc_visit(code, **kwargs):
12
"""
13
Visit the given code with ComplexityVisitor.
14
15
Parameters:
16
- code (str): Python source code to analyze
17
- **kwargs: Keyword arguments passed to ComplexityVisitor
18
- to_method (bool): Whether to treat top-level functions as methods
19
- classname (str): Class name for method analysis
20
- off (bool): Whether to include decorator complexity
21
- no_assert (bool): Whether to exclude assert statements from complexity
22
23
Returns:
24
list: List of Function and Class objects representing code blocks
25
"""
26
27
def cc_visit_ast(ast_node, **kwargs):
28
"""
29
Visit the AST node with ComplexityVisitor.
30
31
Parameters:
32
- ast_node: Python AST node to analyze
33
- **kwargs: Keyword arguments passed to ComplexityVisitor
34
35
Returns:
36
list: List of Function and Class objects representing code blocks
37
"""
38
```
39
40
### Complexity Ranking and Utilities
41
42
Functions for ranking complexity scores and processing analysis results.
43
44
```python { .api }
45
def cc_rank(cc):
46
"""
47
Rank the complexity score from A to F.
48
49
Ranking system:
50
- A (1-5): Low risk - simple block
51
- B (6-10): Low risk - well structured and stable block
52
- C (11-20): Moderate risk - slightly complex block
53
- D (21-30): More than moderate risk - more complex block
54
- E (31-40): High risk - complex block, alarming
55
- F (41+): Very high risk - error-prone, unstable block
56
57
Parameters:
58
- cc (int): Complexity score (must be non-negative)
59
60
Returns:
61
str: Single letter grade (A-F)
62
63
Raises:
64
ValueError: If complexity is negative
65
"""
66
67
def average_complexity(blocks):
68
"""
69
Compute the average cyclomatic complexity from blocks.
70
71
Parameters:
72
- blocks (list): List of Function or Class objects
73
74
Returns:
75
float: Average complexity score, or 0 if blocks is empty
76
"""
77
78
def sorted_results(blocks, order=SCORE):
79
"""
80
Sort blocks by complexity with specified ordering.
81
82
Parameters:
83
- blocks (list): List of Function or Class objects
84
- order (function): Sorting function, one of:
85
- SCORE: Sort by complexity score (descending) - default
86
- LINES: Sort by line number (ascending)
87
- ALPHA: Sort alphabetically by name (ascending)
88
89
Returns:
90
list: Sorted list of blocks
91
"""
92
93
def add_inner_blocks(blocks):
94
"""
95
Process blocks by adding closures and inner classes as top-level blocks.
96
Flattens nested functions and inner classes into the main block list.
97
98
Parameters:
99
- blocks (list): List of Function or Class objects
100
101
Returns:
102
list: Expanded list with nested blocks promoted to top-level
103
"""
104
```
105
106
### Sorting Constants
107
108
Predefined sorting functions for use with sorted_results().
109
110
```python { .api }
111
# Sort by complexity score (descending)
112
SCORE = lambda block: -GET_COMPLEXITY(block)
113
114
# Sort by line number (ascending)
115
LINES = lambda block: block.lineno
116
117
# Sort alphabetically by name (ascending)
118
ALPHA = lambda block: block.name
119
```
120
121
## Usage Examples
122
123
### Basic Complexity Analysis
124
125
```python
126
from radon.complexity import cc_visit, cc_rank
127
128
code = '''
129
def simple_function():
130
return True
131
132
def complex_function(x, y, z):
133
if x > 0:
134
if y > 0:
135
if z > 0:
136
return x + y + z
137
else:
138
return x + y
139
else:
140
return x
141
else:
142
return 0
143
'''
144
145
# Analyze complexity
146
blocks = cc_visit(code)
147
148
for block in blocks:
149
rank = cc_rank(block.complexity)
150
print(f"{block.name}: {block.complexity} ({rank})")
151
# Output:
152
# simple_function: 1 (A)
153
# complex_function: 4 (A)
154
```
155
156
### Processing and Sorting Results
157
158
```python
159
from radon.complexity import cc_visit, sorted_results, average_complexity, SCORE, LINES, ALPHA
160
161
code = '''
162
class Calculator:
163
def add(self, a, b):
164
return a + b
165
166
def complex_divide(self, a, b):
167
if b == 0:
168
raise ValueError("Division by zero")
169
elif isinstance(a, str) or isinstance(b, str):
170
raise TypeError("String division not supported")
171
else:
172
return a / b
173
174
def utility_function():
175
pass
176
'''
177
178
blocks = cc_visit(code)
179
180
# Sort by complexity (default)
181
by_complexity = sorted_results(blocks, SCORE)
182
print("By complexity:")
183
for block in by_complexity:
184
print(f" {block.name}: {block.complexity}")
185
186
# Sort by line number
187
by_lines = sorted_results(blocks, LINES)
188
print("By line number:")
189
for block in by_lines:
190
print(f" Line {block.lineno}: {block.name}")
191
192
# Sort alphabetically
193
by_name = sorted_results(blocks, ALPHA)
194
print("By name:")
195
for block in by_name:
196
print(f" {block.name}: {block.complexity}")
197
198
# Calculate average complexity
199
avg = average_complexity(blocks)
200
print(f"Average complexity: {avg:.2f}")
201
```
202
203
### Analyzing Methods vs Functions
204
205
```python
206
from radon.complexity import cc_visit
207
208
# Analyze as standalone functions
209
blocks_as_functions = cc_visit(class_code, to_method=False)
210
211
# Analyze as methods within a class context
212
blocks_as_methods = cc_visit(class_code, to_method=True, classname="MyClass")
213
214
for block in blocks_as_methods:
215
if block.is_method:
216
print(f"Method {block.fullname}: {block.complexity}")
217
else:
218
print(f"Function {block.name}: {block.complexity}")
219
```
220
221
### Handling Nested Functions and Classes
222
223
```python
224
from radon.complexity import cc_visit, add_inner_blocks
225
226
code_with_nested = '''
227
class OuterClass:
228
def method(self):
229
def inner_function():
230
return True
231
return inner_function()
232
233
class InnerClass:
234
def inner_method(self):
235
pass
236
'''
237
238
# Get blocks including nested structures
239
blocks = cc_visit(code_with_nested)
240
241
# Flatten nested blocks to top level
242
all_blocks = add_inner_blocks(blocks)
243
244
print("All blocks (including nested):")
245
for block in all_blocks:
246
print(f" {block.name}: {block.complexity}")
247
```
248
249
## Error Handling
250
251
The complexity analysis functions handle various error conditions:
252
253
- **Invalid complexity scores**: `cc_rank()` raises `ValueError` for negative complexity values
254
- **Empty block lists**: `average_complexity()` returns 0 for empty lists
255
- **Malformed code**: AST parsing errors are propagated from underlying Python ast module
256
- **Invalid sorting functions**: TypeError if order parameter is not callable
257
258
## Integration with CLI
259
260
The complexity module integrates with radon's command-line interface:
261
262
```bash
263
# Command-line equivalent of cc_visit()
264
radon cc path/to/code.py
265
266
# With ranking and complexity display
267
radon cc --show-complexity --min A --max F path/to/code.py
268
269
# JSON output for programmatic processing
270
radon cc --json path/to/code.py
271
```