A flake8 plugin to help you write better list/set/dict comprehensions.
npx @tessl/cli install tessl/pypi-flake8-comprehensions@3.16.0A 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.
pip install flake8-comprehensionsThis is a flake8 plugin, so it's not typically imported directly in code. Instead, it integrates automatically with flake8:
# The plugin is automatically loaded by flake8
# No direct import needed in your codeFor direct usage (advanced/testing):
from flake8_comprehensions import ComprehensionChecker
import astAfter installation, the plugin automatically integrates with flake8 and detects comprehension optimization opportunities:
# Install the plugin
pip install flake8-comprehensions
# Run flake8 - C4xx rules are automatically active
flake8 your_code.py
# Example output:
# your_code.py:5:8: C400 Unnecessary generator - rewrite as a list comprehension.
# your_code.py:12:15: C403 Unnecessary list comprehension - rewrite as a set comprehension.Configure in setup.cfg or pyproject.toml:
[flake8]
select = C4 # Enable only comprehension rules
# or
ignore = C415,C416 # Disable specific rulesimport ast
from flake8_comprehensions import ComprehensionChecker
# Parse Python code
code = "foo = list(x + 1 for x in range(10))"
tree = ast.parse(code)
# Create checker instance
checker = ComprehensionChecker(tree)
# Run analysis
for violation in checker.run():
line, col, message, checker_type = violation
print(f"Line {line}, Col {col}: {message}")The plugin follows flake8's standard plugin architecture and uses Python's AST (Abstract Syntax Tree) for code analysis:
flake8.extension with code prefix C4ComprehensionChecker instance, passing the parsed ASTrun() method which yields tuples of (line, column, message, checker_type)ast.walk() to traverse all nodes in the Python ASTast.Call nodes with generators)This architecture enables static analysis of Python code without execution, providing fast and reliable detection of comprehension optimization opportunities.
The main plugin class that analyzes Python AST for comprehension optimization opportunities.
class ComprehensionChecker:
"""
Flake8 plugin to help you write better list/set/dict comprehensions.
"""
name: str # "flake8-comprehensions"
version: str # Plugin version from package metadata
messages: dict[str, str] # Error message templates for all C4xx rules
tree: ast.AST # Python AST tree to analyze
def __init__(self, tree: ast.AST) -> None:
"""Initialize checker with AST tree."""
def run(self) -> Generator[tuple[int, int, str, type[Any]]]:
"""
Main analysis method that yields flake8 violations.
Yields:
tuple[int, int, str, type]: (line_number, column_offset, message, checker_type)
"""Utility functions used internally by the checker:
def has_star_args(call_node: ast.Call) -> bool:
"""Check if AST Call node has starred arguments (*args)."""
def has_double_star_args(call_node: ast.Call) -> bool:
"""Check if AST Call node has double-starred arguments (**kwargs)."""comp_type: dict[type[ast.AST], str]
# Maps AST comprehension node types to string names
# {ast.DictComp: "dict", ast.ListComp: "list", ast.SetComp: "set"}The plugin implements 20 specific rules for comprehension optimization:
list(f(x) for x in foo) → [f(x) for x in foo]set(f(x) for x in foo) → {f(x) for x in foo}dict((x, f(x)) for x in foo) → {x: f(x) for x in foo}set([f(x) for x in foo]) → {f(x) for x in foo}dict([(x, f(x)) for x in foo]) → {x: f(x) for x in foo}set([1, 2]) → {1, 2}, set((1, 2)) → {1, 2}dict([(1, 2)]) → {1: 2}, dict(((1, 2),)) → {1: 2}dict() → {}, list() → [], tuple() → ()tuple((1, 2)) → (1, 2), tuple([1, 2]) → (1, 2)list([1, 2]) → [1, 2], list((1, 2)) → [1, 2]list([f(x) for x in foo]) → [f(x) for x in foo]list(sorted([2, 3, 1])) → sorted([2, 3, 1])reversed(sorted([2, 3, 1])) → sorted([2, 3, 1], reverse=True)list(list(iterable)) → list(iterable)set(sorted(iterable)) → set(iterable)set(iterable[::-1]) → set(iterable)reversed(iterable[::-1]) → iterable[x for x in iterable] → list(iterable){x for x in iterable} → set(iterable){a: b for a, b in iterable} → dict(iterable){x: 1 for x in iterable} → dict.fromkeys(iterable, 1){x: None for x in iterable} → dict.fromkeys(iterable)map(lambda x: x + 1, iterable) → (x + 1 for x in iterable)list(map(lambda x: x * 2, nums)) → [x * 2 for x in nums]dict({}) → {}, dict({"a": 1}) → {"a": 1}all([condition(x) for x in iterable]) → all(condition(x) for x in iterable)any([condition(x) for x in iterable]) → any(condition(x) for x in iterable)The plugin integrates with flake8 through Python's entry point system:
# pyproject.toml entry point configuration
[project.entry-points."flake8.extension"]
C4 = "flake8_comprehensions:ComprehensionChecker"This registers the plugin with flake8 to handle all C4xx rule codes. The plugin follows flake8's standard interface:
ComprehensionChecker instance for each Python file, passing the parsed ASTrun() method to get a generator of violations(line, column, message, checker_type)# C400: Generator to list comprehension
# Bad
numbers = list(x * 2 for x in range(10))
# Good
numbers = [x * 2 for x in range(10)]
# C403: List comprehension to set comprehension
# Bad
unique_squares = set([x**2 for x in numbers])
# Good
unique_squares = {x**2 for x in numbers}
# C411: Unnecessary list() around list comprehension
# Bad
processed = list([process(x) for x in items])
# Good
processed = [process(x) for x in items]
# C417: Map with lambda to comprehension
# Bad
doubled = list(map(lambda x: x * 2, numbers))
# Good
doubled = [x * 2 for x in numbers]
# C419: List comprehension in any/all
# Bad - builds entire list before checking
valid = all([is_valid(item) for item in items])
# Good - stops at first False
valid = all(is_valid(item) for item in items)# In pre-commit hooks
repos:
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies: [flake8-comprehensions]
# In CI/CD pipelines
flake8 --select=C4 src/
# Returns exit code 1 if any C4xx violations found
# With specific rule configuration
flake8 --ignore=C416,C419 src/ # Ignore specific rules
flake8 --select=C400,C401,C402 src/ # Only generator rules