Skip to content

Commit 867247f

Browse files
authored
Add rule for list comprehensions passed to any()/all() (#427)
1 parent b6c2b95 commit 867247f

File tree

4 files changed

+64
-3
lines changed

4 files changed

+64
-3
lines changed

CHANGELOG.rst

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Changelog
44

55
* Add rule C418 to check for calls passing a dict literal or dict comprehension to ``dict()``.
66

7+
* Add rule C419 to check for calls passing a list comprehension to ``any()``/``all()``.
8+
79
3.11.1 (2023-03-21)
810
-------------------
911

README.rst

+11
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,14 @@ For example:
216216

217217
* Rewrite ``dict({})`` as ``{}``
218218
* Rewrite ``dict({"a": 1})`` as ``{"a": 1}``
219+
220+
C419 Unnecessary list comprehension in ``<any/all>``\() prevents short-circuiting - rewrite as a generator.
221+
-----------------------------------------------------------------------------------------------------------
222+
223+
Using a list comprehension inside a call to ``any()``/``all()`` prevents short-circuiting when a ``True`` / ``False`` value is found.
224+
The whole list will be constructed before calling ``any()``/``all()``, potentially wasting work.part-way.
225+
Rewrite to use a generator expression, which can stop part way.
226+
For example:
227+
228+
* Rewrite ``all([condition(x) for x in iterable])`` as ``all(condition(x) for x in iterable)``
229+
* Rewrite ``any([condition(x) for x in iterable])`` as ``any(condition(x) for x in iterable)``

src/flake8_comprehensions/__init__.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ def __init__(self, tree: ast.AST) -> None:
4747
"C418 Unnecessary {type} passed to dict() - "
4848
+ "remove the outer call to dict()."
4949
),
50+
"C419": (
51+
"C419 Unnecessary list comprehension passed to {func}() prevents "
52+
+ "short-circuiting - rewrite as a generator."
53+
),
5054
}
5155

5256
def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]:
@@ -93,13 +97,19 @@ def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]:
9397
elif (
9498
num_positional_args == 1
9599
and isinstance(node.args[0], ast.ListComp)
96-
and node.func.id in ("list", "set")
100+
and node.func.id in ("list", "set", "any", "all")
97101
):
98-
msg_key = {"list": "C411", "set": "C403"}[node.func.id]
102+
msg_key = {
103+
"list": "C411",
104+
"set": "C403",
105+
"any": "C419",
106+
"all": "C419",
107+
}[node.func.id]
108+
msg = self.messages[msg_key].format(func=node.func.id)
99109
yield (
100110
node.lineno,
101111
node.col_offset,
102-
self.messages[msg_key],
112+
msg,
103113
type(self),
104114
)
105115

tests/test_flake8_comprehensions.py

+38
Original file line numberDiff line numberDiff line change
@@ -938,3 +938,41 @@ def test_C418_fail(code, failures, flake8_path):
938938
(flake8_path / "example.py").write_text(dedent(code))
939939
result = flake8_path.run_flake8()
940940
assert result.out_lines == failures
941+
942+
943+
@pytest.mark.parametrize(
944+
"code",
945+
[
946+
"any(num == 3 for num in range(5))",
947+
"all(num == 3 for num in range(5))",
948+
],
949+
)
950+
def test_C419_pass(code, flake8_path):
951+
(flake8_path / "example.py").write_text(dedent(code))
952+
result = flake8_path.run_flake8()
953+
assert result.out_lines == []
954+
955+
956+
@pytest.mark.parametrize(
957+
"code,failures",
958+
[
959+
(
960+
"any([num == 3 for num in range(5)])",
961+
[
962+
"./example.py:1:1: C419 Unnecessary list comprehension passed "
963+
+ "to any() prevents short-circuiting - rewrite as a generator."
964+
],
965+
),
966+
(
967+
"all([num == 3 for num in range(5)])",
968+
[
969+
"./example.py:1:1: C419 Unnecessary list comprehension passed "
970+
+ "to all() prevents short-circuiting - rewrite as a generator."
971+
],
972+
),
973+
],
974+
)
975+
def test_C419_fail(code, failures, flake8_path):
976+
(flake8_path / "example.py").write_text(dedent(code))
977+
result = flake8_path.run_flake8()
978+
assert result.out_lines == failures

0 commit comments

Comments
 (0)