B020: don't flag rebinding an attribute of the loop's base object - #568
B020: don't flag rebinding an attribute of the loop's base object#568Eljees wants to merge 1 commit into
Conversation
`for self.a in self.b` rebinds the attribute `a`, not the name `self`, so comparing the bare base name reported every loop over a sibling attribute of the same object. Compare the whole dotted path instead, and ignore names that only ever appear in load context (they are the base of an attribute target, not something the loop rebinds). Fixes PyCQA#248
There was a problem hiding this comment.
Pull request overview
Fixes B020 false positives when loop targets and iterables use different attributes of the same object.
Changes:
- Compares complete dotted attribute paths.
- Ignores load-only target names.
- Adds regression cases and changelog documentation.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
bugbear.py |
Updates B020 attribute-target analysis. |
tests/eval_files/b020.py |
Adds attribute-target regression cases. |
README.rst |
Documents the B020 fix. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| for sub in ast.walk(node.iter): | ||
| if isinstance(sub, ast.Attribute): | ||
| path = _dotted_name(sub) | ||
| if path is not None: | ||
| iterset_names.add(path) |
| print(self.value) | ||
|
|
||
| def still_an_error(self): | ||
| for self.test_suite in self.test_suite: # B020: 12, "self.test_suite" |
cooperlees
left a comment
There was a problem hiding this comment.
LGTM - Thanks for this.
I think copilot has found some nice performance things especially to polish up here - As always, feel free to state why it's wrong tho if it is.
|
Thanks. Both are right. The scope point is the substantive one: I collected dotted paths with The stale |
Fixes #248.
The false positive
for self.a in self.brebinds the attributea— it does not rebind the nameself.check_for_b020compared the bare base name of the loop target against the names in theiterable, so
selfmatchedself, and every loop over a sibling attribute of the sameobject was reported:
self.model_instance.valueandself.test_suiteare two different bindings, so this loopcannot reassign the thing it is iterating.
The fix
Two changes in
check_for_b020:self.model_instance.valueagainstself.test_suite)rather than the base name, on both the target and the iterable side;
attribute or subscript target, not something the loop rebinds.
What still errors
The case the check exists for is unchanged, and there is an eval case pinning it:
Checks
tests/eval_files/b020.pygains three cases — two that must now be silent and one thatmust still error. Reverting only
bugbear.pymakes the file fail with exactly the twoextra
B020s, so the new cases do pin this fix rather than passing incidentally.2155484: 79 passed, 2 skipped — same asmain.pre-commit run --all-files: isort, black, flake8, rstcheck all pass.README.rstUNRELEASEDupdated.@cooperlees — you wrote on the issue that you weren't sure this one could be fixed.
Comparing full dotted paths turned out to be enough; happy to adjust the approach if you'd
rather this stayed a known limitation.