Skip to content

Commit f1c391a

Browse files
NewGladAlexey Nikitin
andauthored
Add exclusions for B905 (#388)
Co-authored-by: Alexey Nikitin <[email protected]>
1 parent a224d62 commit f1c391a

File tree

4 files changed

+58
-7
lines changed

4 files changed

+58
-7
lines changed

README.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,11 @@ See `the exception chaining tutorial <https://docs.python.org/3/tutorial/errors.
220220
for details.
221221

222222
**B905**: ``zip()`` without an explicit `strict=` parameter set. ``strict=True`` causes the resulting iterator
223-
to raise a ``ValueError`` if the arguments are exhausted at differing lengths. The ``strict=`` argument
224-
was added in Python 3.10, so don't enable this flag for code that should work on <3.10.
223+
to raise a ``ValueError`` if the arguments are exhausted at differing lengths.
224+
225+
Exclusions are `itertools.count <https://docs.python.org/3/library/itertools.html#itertools.count>`_, `itertools.cycle <https://docs.python.org/3/library/itertools.html#itertools.cycle>`_ and `itertools.repeat <https://docs.python.org/3/library/itertools.html#itertools.repeat>`_ (with times=None) since they are infinite iterators.
226+
227+
The ``strict=`` argument was added in Python 3.10, so don't enable this flag for code that should work on <3.10.
225228
For more information: https://peps.python.org/pep-0618/
226229

227230
**B906**: ``visit_`` function with no further call to a ``visit`` function. This is often an error, and will stop the visitor from recursing into the subnodes of a visited node. Consider adding a call ``self.generic_visit(node)`` at the end of the function.

bugbear.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,12 +1169,46 @@ def check_for_b025(self, node):
11691169
for duplicate in duplicates:
11701170
self.errors.append(B025(node.lineno, node.col_offset, vars=(duplicate,)))
11711171

1172-
def check_for_b905(self, node):
1173-
if (
1174-
isinstance(node.func, ast.Name)
1175-
and node.func.id == "zip"
1176-
and not any(kw.arg == "strict" for kw in node.keywords)
1172+
@staticmethod
1173+
def _is_infinite_iterator(node: ast.expr) -> bool:
1174+
if not (
1175+
isinstance(node, ast.Call)
1176+
and isinstance(node.func, ast.Attribute)
1177+
and isinstance(node.func.value, ast.Name)
1178+
and node.func.value.id == "itertools"
11771179
):
1180+
return False
1181+
if node.func.attr in {"cycle", "count"}:
1182+
return True
1183+
elif node.func.attr == "repeat":
1184+
if len(node.args) == 1 and len(node.keywords) == 0:
1185+
# itertools.repeat(iterable)
1186+
return True
1187+
if (
1188+
len(node.args) == 2
1189+
and isinstance(node.args[1], ast.Constant)
1190+
and node.args[1].value is None
1191+
):
1192+
# itertools.repeat(iterable, None)
1193+
return True
1194+
for kw in node.keywords:
1195+
# itertools.repeat(iterable, times=None)
1196+
if (
1197+
kw.arg == "times"
1198+
and isinstance(kw.value, ast.Constant)
1199+
and kw.value.value is None
1200+
):
1201+
return True
1202+
1203+
return False
1204+
1205+
def check_for_b905(self, node):
1206+
if not (isinstance(node.func, ast.Name) and node.func.id == "zip"):
1207+
return
1208+
for arg in node.args:
1209+
if self._is_infinite_iterator(arg):
1210+
return
1211+
if not any(kw.arg == "strict" for kw in node.keywords):
11781212
self.errors.append(B905(node.lineno, node.col_offset))
11791213

11801214
def check_for_b906(self, node: ast.FunctionDef):

tests/b905_py310.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,15 @@
88
zip(range(3), strict=True)
99
zip("a", "b", strict=False)
1010
zip("a", "b", "c", strict=True)
11+
12+
# infinite iterators from itertools module should not raise errors
13+
import itertools
14+
15+
zip([1, 2, 3], itertools.cycle("ABCDEF"))
16+
zip([1, 2, 3], itertools.count())
17+
zip([1, 2, 3], itertools.repeat(1))
18+
zip([1, 2, 3], itertools.repeat(1, None))
19+
zip([1, 2, 3], itertools.repeat(1, times=None))
20+
21+
zip([1, 2, 3], itertools.repeat(1, 1))
22+
zip([1, 2, 3], itertools.repeat(1, times=4))

tests/test_bugbear.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,8 @@ def test_b905(self):
700700
B905(4, 15),
701701
B905(5, 4),
702702
B905(6, 0),
703+
B905(21, 0),
704+
B905(22, 0),
703705
]
704706
self.assertEqual(errors, self.errors(*expected))
705707

0 commit comments

Comments
 (0)