Skip to content

B911: don't flag a bare batched() the module binds to something else - #608

Merged
cooperlees merged 2 commits into
PyCQA:mainfrom
LuisFigueroaG:fix/b911-shadowed-batched
Oct 27, 2026
Merged

cooperlees merged 2 commits into
PyCQA:mainfrom
LuisFigueroaG:fix/b911-shadowed-batched

Conversation

@LuisFigueroaG

@LuisFigueroaG LuisFigueroaG commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

B911 treats every bare batched() call as itertools.batched. Projects that still support Python < 3.12 often ship their own backport, which has no strict parameter, and they get B911 on every call:

# fontTools/misc/iterTools.py
def batched(iterable, n): ...

# fontTools/ttLib/scaleUpem.py
from fontTools.misc.iterTools import batched

for batch in batched(items, 2):  # B911, but this batched() takes no strict=
    ...

B031 already handles the same thing for groupby() (#356): a bare groupby only counts as itertools when the module imports it from itertools or doesn't bind the name at all. This turns that helper into one that takes the name, and B911 now uses it for batched. It also recognises batched = itertools.batched assignments (including inside try/except), which B031 gets for groupby as well. Some things are still reported:

  • itertools.batched(...) calls.
  • from itertools import batched and from itertools import *.
  • A try: from itertools import batched / except ImportError: def batched(...) fallback, since on newer Pythons the name really is itertools.batched.
  • batched = itertools.batched, also as a try/except AttributeError fallback.

On about 48,000 files (site-packages from eight project virtualenvs, Ubuntu's Python 3.12 stdlib and the repos in my workspace), B911 reports drop from 30 to 4 and B031 stays at 14. Nothing new is reported. The 26 removed reports come from three sources, each installed in several venvs: fontTools (backport with no strict), blosc2 (batched imported from its own proxy_source backport) and the copy of more_itertools vendored in setuptools, which defines its own batched wrapper.

New cases are in tests/eval_files/b911_shadowed*.py and tests/eval_files/b031_shadowed_alias.py. Three of them fail on main (the B911 alias cases pass there and guard the regression). tox py310–py315, mypy and pep8_naming pass, and so does pre-commit.

A bare batched() call was always treated as itertools.batched, so a
module with its own `def batched(iterable, n)` (a common backport for
Pythons before 3.12) or one imported from another library got B911 for
a function that has no strict parameter.

B911 now reuses the check B031 already does for groupby(): the bare name
counts as itertools.batched only when the module imports it from
itertools or doesn't bind it at all. A try/except fallback that imports
it from itertools is still reported, and itertools.batched(...) calls
are always checked.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The guard incorrectly suppresses B911 diagnostics for assignment-based itertools.batched aliases, including try/except fallbacks, requiring a fix and regression coverage that preserves B031 behavior.

1 open issue
What changed in this PR

This PR reduces B911 false positives for custom batched() implementations by reusing B031’s module-level binding check.

Changes:

  • Generalizes the itertools name resolver and its cache for use by B031 and B911.
  • Adds regression cases for local definitions, third-party imports, and import fallbacks.
  • Documents the B911 behavior change in the unreleased changelog.
File Description
tests/​eval_files/​b911_shadowed.py Covers a locally defined batched and a qualified itertools call.
tests/​eval_files/​b911_shadowed_import.py Covers a third-party batched import.
tests/​eval_files/​b911_shadowed_fallback.py Keeps B911 reporting for an itertools import with a backport fallback.
README.rst Adds the B911 changelog entry.
bugbear.py Shares itertools binding resolution between B031 and B911.

Review effort: Balanced

Comment thread bugbear.py
(
isinstance(node.func, ast.Name)
and node.func.id == "batched"
and self._bare_name_is_itertools("batched")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed: batched = itertools.batched (also inside a try/except fallback) now counts as itertools again, with new eval files for both. B031 gets the same through the shared helper, so groupby = itertools.groupby is now covered too.

@cooperlees cooperlees left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again!

@cooperlees
cooperlees merged commit 8cab70c into PyCQA:main Oct 27, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants