A flake8 plugin to help you write better list/set/dict comprehensions.
npx @tessl/cli install tessl/pypi-flake8-comprehensions@3.16.00
# flake8-comprehensions
1
2
A flake8 plugin that helps you write better list, set, and dict comprehensions in Python. The plugin implements over 20 specific rules (C400-C420) that detect unnecessary generators, redundant comprehensions, inefficient patterns, and suboptimal code constructs when working with Python's comprehension syntax.
3
4
## Package Information
5
6
- **Package Name**: flake8-comprehensions
7
- **Package Type**: pypi
8
- **Language**: Python
9
- **Installation**: `pip install flake8-comprehensions`
10
- **Requirements**: Python 3.9+, flake8 >=3 (!=3.2)
11
12
## Core Imports
13
14
This is a flake8 plugin, so it's not typically imported directly in code. Instead, it integrates automatically with flake8:
15
16
```python
17
# The plugin is automatically loaded by flake8
18
# No direct import needed in your code
19
```
20
21
For direct usage (advanced/testing):
22
23
```python
24
from flake8_comprehensions import ComprehensionChecker
25
import ast
26
```
27
28
## Basic Usage
29
30
### As a flake8 Plugin (Normal Usage)
31
32
After installation, the plugin automatically integrates with flake8 and detects comprehension optimization opportunities:
33
34
```bash
35
# Install the plugin
36
pip install flake8-comprehensions
37
38
# Run flake8 - C4xx rules are automatically active
39
flake8 your_code.py
40
41
# Example output:
42
# your_code.py:5:8: C400 Unnecessary generator - rewrite as a list comprehension.
43
# your_code.py:12:15: C403 Unnecessary list comprehension - rewrite as a set comprehension.
44
```
45
46
Configure in setup.cfg or pyproject.toml:
47
48
```ini
49
[flake8]
50
select = C4 # Enable only comprehension rules
51
# or
52
ignore = C415,C416 # Disable specific rules
53
```
54
55
### Direct API Usage (Advanced)
56
57
```python
58
import ast
59
from flake8_comprehensions import ComprehensionChecker
60
61
# Parse Python code
62
code = "foo = list(x + 1 for x in range(10))"
63
tree = ast.parse(code)
64
65
# Create checker instance
66
checker = ComprehensionChecker(tree)
67
68
# Run analysis
69
for violation in checker.run():
70
line, col, message, checker_type = violation
71
print(f"Line {line}, Col {col}: {message}")
72
```
73
74
## Architecture
75
76
The plugin follows flake8's standard plugin architecture and uses Python's AST (Abstract Syntax Tree) for code analysis:
77
78
### Plugin Integration
79
- **Entry Point Registration**: The plugin registers with flake8 via Python's entry point system under `flake8.extension` with code prefix `C4`
80
- **Instantiation**: For each Python file, flake8 creates a `ComprehensionChecker` instance, passing the parsed AST
81
- **Analysis**: flake8 calls the `run()` method which yields tuples of `(line, column, message, checker_type)`
82
- **Reporting**: flake8 formats and reports violations according to user configuration
83
84
### AST Analysis Pattern
85
- **Tree Walking**: Uses `ast.walk()` to traverse all nodes in the Python AST
86
- **Pattern Matching**: Identifies specific AST node patterns (e.g., `ast.Call` nodes with generators)
87
- **Rule Logic**: Each C4xx rule corresponds to specific combinations of AST node types and attributes
88
- **Violation Detection**: When a problematic pattern is found, yields structured violation data
89
90
### Error Code Organization
91
- **C400-C402**: Generator to comprehension transformations
92
- **C403-C404**: List comprehension to other comprehension types
93
- **C405-C420**: Various literal, constructor, and optimization patterns
94
95
This architecture enables static analysis of Python code without execution, providing fast and reliable detection of comprehension optimization opportunities.
96
97
## Capabilities
98
99
### ComprehensionChecker Class
100
101
The main plugin class that analyzes Python AST for comprehension optimization opportunities.
102
103
```python { .api }
104
class ComprehensionChecker:
105
"""
106
Flake8 plugin to help you write better list/set/dict comprehensions.
107
"""
108
109
name: str # "flake8-comprehensions"
110
version: str # Plugin version from package metadata
111
messages: dict[str, str] # Error message templates for all C4xx rules
112
tree: ast.AST # Python AST tree to analyze
113
114
def __init__(self, tree: ast.AST) -> None:
115
"""Initialize checker with AST tree."""
116
117
def run(self) -> Generator[tuple[int, int, str, type[Any]]]:
118
"""
119
Main analysis method that yields flake8 violations.
120
121
Yields:
122
tuple[int, int, str, type]: (line_number, column_offset, message, checker_type)
123
"""
124
```
125
126
### Helper Functions
127
128
Utility functions used internally by the checker:
129
130
```python { .api }
131
def has_star_args(call_node: ast.Call) -> bool:
132
"""Check if AST Call node has starred arguments (*args)."""
133
134
def has_double_star_args(call_node: ast.Call) -> bool:
135
"""Check if AST Call node has double-starred arguments (**kwargs)."""
136
```
137
138
### Constants
139
140
```python { .api }
141
comp_type: dict[type[ast.AST], str]
142
# Maps AST comprehension node types to string names
143
# {ast.DictComp: "dict", ast.ListComp: "list", ast.SetComp: "set"}
144
```
145
146
## Error Rules Reference
147
148
The plugin implements 20 specific rules for comprehension optimization:
149
150
### Generator to Comprehension Rules (C400-C402)
151
- **C400**: Unnecessary generator - rewrite as list comprehension
152
- `list(f(x) for x in foo)` → `[f(x) for x in foo]`
153
- **C401**: Unnecessary generator - rewrite as set comprehension
154
- `set(f(x) for x in foo)` → `{f(x) for x in foo}`
155
- **C402**: Unnecessary generator - rewrite as dict comprehension
156
- `dict((x, f(x)) for x in foo)` → `{x: f(x) for x in foo}`
157
158
### List to Other Comprehension Rules (C403-C404)
159
- **C403**: Unnecessary list comprehension - rewrite as set comprehension
160
- `set([f(x) for x in foo])` → `{f(x) for x in foo}`
161
- **C404**: Unnecessary list comprehension - rewrite as dict comprehension
162
- `dict([(x, f(x)) for x in foo])` → `{x: f(x) for x in foo}`
163
164
### Literal Optimization Rules (C405-C406)
165
- **C405**: Unnecessary list/tuple literal - rewrite as set literal
166
- `set([1, 2])` → `{1, 2}`, `set((1, 2))` → `{1, 2}`
167
- **C406**: Unnecessary list/tuple literal - rewrite as dict literal
168
- `dict([(1, 2)])` → `{1: 2}`, `dict(((1, 2),))` → `{1: 2}`
169
170
### Empty Constructor Rules (C408)
171
- **C408**: Unnecessary call - rewrite as literal
172
- `dict()` → `{}`, `list()` → `[]`, `tuple()` → `()`
173
174
### Redundant Constructor Rules (C409-C411)
175
- **C409**: Unnecessary tuple passed to tuple() - remove outer call or rewrite as literal
176
- `tuple((1, 2))` → `(1, 2)`, `tuple([1, 2])` → `(1, 2)`
177
- **C410**: Unnecessary list passed to list() - remove outer call or rewrite as literal
178
- `list([1, 2])` → `[1, 2]`, `list((1, 2))` → `[1, 2]`
179
- **C411**: Unnecessary list call - remove outer call to list()
180
- `list([f(x) for x in foo])` → `[f(x) for x in foo]`
181
182
### Function Call Optimization Rules (C413-C415)
183
- **C413**: Unnecessary list/reversed call around sorted()
184
- `list(sorted([2, 3, 1]))` → `sorted([2, 3, 1])`
185
- `reversed(sorted([2, 3, 1]))` → `sorted([2, 3, 1], reverse=True)`
186
- **C414**: Unnecessary call within other constructor calls
187
- `list(list(iterable))` → `list(iterable)`
188
- `set(sorted(iterable))` → `set(iterable)`
189
- **C415**: Unnecessary subscript reversal within functions
190
- `set(iterable[::-1])` → `set(iterable)`
191
- `reversed(iterable[::-1])` → `iterable`
192
193
### Identity Comprehension Rules (C416, C420)
194
- **C416**: Unnecessary comprehension - rewrite using constructor
195
- `[x for x in iterable]` → `list(iterable)`
196
- `{x for x in iterable}` → `set(iterable)`
197
- `{a: b for a, b in iterable}` → `dict(iterable)`
198
- **C420**: Unnecessary dict comprehension - rewrite using dict.fromkeys()
199
- `{x: 1 for x in iterable}` → `dict.fromkeys(iterable, 1)`
200
- `{x: None for x in iterable}` → `dict.fromkeys(iterable)`
201
202
### Lambda and Map Rules (C417)
203
- **C417**: Unnecessary map usage - rewrite using comprehension
204
- `map(lambda x: x + 1, iterable)` → `(x + 1 for x in iterable)`
205
- `list(map(lambda x: x * 2, nums))` → `[x * 2 for x in nums]`
206
207
### Dict Constructor Rules (C418)
208
- **C418**: Unnecessary dict/dict comprehension passed to dict()
209
- `dict({})` → `{}`, `dict({"a": 1})` → `{"a": 1}`
210
211
### Short-circuiting Rules (C419)
212
- **C419**: Unnecessary list comprehension in any/all() prevents short-circuiting
213
- `all([condition(x) for x in iterable])` → `all(condition(x) for x in iterable)`
214
- `any([condition(x) for x in iterable])` → `any(condition(x) for x in iterable)`
215
216
## Plugin Integration
217
218
The plugin integrates with flake8 through Python's entry point system:
219
220
```toml
221
# pyproject.toml entry point configuration
222
[project.entry-points."flake8.extension"]
223
C4 = "flake8_comprehensions:ComprehensionChecker"
224
```
225
226
This registers the plugin with flake8 to handle all C4xx rule codes. The plugin follows flake8's standard interface:
227
228
1. **Instantiation**: flake8 creates a `ComprehensionChecker` instance for each Python file, passing the parsed AST
229
2. **Analysis**: flake8 calls the `run()` method to get a generator of violations
230
3. **Reporting**: Each violation is a tuple of `(line, column, message, checker_type)`
231
232
## Usage Examples
233
234
### Common Patterns Detected
235
236
```python
237
# C400: Generator to list comprehension
238
# Bad
239
numbers = list(x * 2 for x in range(10))
240
# Good
241
numbers = [x * 2 for x in range(10)]
242
243
# C403: List comprehension to set comprehension
244
# Bad
245
unique_squares = set([x**2 for x in numbers])
246
# Good
247
unique_squares = {x**2 for x in numbers}
248
249
# C411: Unnecessary list() around list comprehension
250
# Bad
251
processed = list([process(x) for x in items])
252
# Good
253
processed = [process(x) for x in items]
254
255
# C417: Map with lambda to comprehension
256
# Bad
257
doubled = list(map(lambda x: x * 2, numbers))
258
# Good
259
doubled = [x * 2 for x in numbers]
260
261
# C419: List comprehension in any/all
262
# Bad - builds entire list before checking
263
valid = all([is_valid(item) for item in items])
264
# Good - stops at first False
265
valid = all(is_valid(item) for item in items)
266
```
267
268
### Integration with Development Workflow
269
270
```bash
271
# In pre-commit hooks
272
repos:
273
- repo: https://github.com/PyCQA/flake8
274
rev: 6.0.0
275
hooks:
276
- id: flake8
277
additional_dependencies: [flake8-comprehensions]
278
279
# In CI/CD pipelines
280
flake8 --select=C4 src/
281
# Returns exit code 1 if any C4xx violations found
282
283
# With specific rule configuration
284
flake8 --ignore=C416,C419 src/ # Ignore specific rules
285
flake8 --select=C400,C401,C402 src/ # Only generator rules
286
```