Repository navigation
B018: report generator expressions and lambdas used as statements - #604
Conversation
A generator expression is lazy, so `(print(x) for x in items)` on its own line never runs its body, and a lambda that is neither assigned nor passed anywhere can never be called. Both are now reported like the other useless expressions. A generator expression directly in a `try` or `with` block is left alone, like operations are, since it calls `iter()` on its first iterable right away and a test may build one only to see that raise. Refs PyCQA#452
There was a problem hiding this comment.
🟡 Changes recommended
Eager lambda defaults and generator iterable expressions can trigger false-positive diagnostics.
2 open issues
What changed in this PR
Extends B018 to detect discarded generator expressions and lambdas, continuing the useless-expression coverage from #452.
Changes:
- Adds detection while exempting generators directly inside try/with blocks.
- Adds diagnostic fixtures and updates an existing lambda expectation.
- Documents the expanded coverage and changelog entry.
| File | Description |
|---|---|
| tests/eval_files/b023.py | Adds B018 expectation for a bare lambda. |
| tests/eval_files/b018_generators_lambdas.py | Covers discarded expressions, used values, and generator exemptions. |
| README.rst | Documents expanded B018 coverage. |
| bugbear.py | Adds generator-expression and lambda checks. |
Review effort: Balanced
| and node.value.func.id in FUNCTIONS_WITHOUT_SIDE_EFFECTS | ||
| ) | ||
| # a lambda that is neither assigned nor passed anywhere is never called | ||
| or isinstance(node.value, ast.Lambda) |
There was a problem hiding this comment.
Fixed in d97dad0: lambda defaults (positional and keyword-only) now go through the same side-effect check as operands, and a lambda with defaults directly in a try/with block is left alone. A lambda without defaults is still reported there, since it evaluates nothing. Both forms have regression cases.
| and _is_useless_operation(node.value) | ||
| ) | ||
| # a generator expression is lazy, so its body never runs | ||
| or isinstance(node.value, ast.GeneratorExp) |
There was a problem hiding this comment.
Fixed in d97dad0: only generators[0].iter goes through the side-effect check, so (x for x in refresh()) and (x for x in await refresh()) are no longer reported, while calls in the body, filters or inner loops still are. Regression cases added.
…away A generator expression evaluates its first iterable as soon as it's created, and a lambda its defaults, so `(x for x in refresh())` still calls `refresh()` and `lambda arg=record(): None` still calls `record()`. Check those parts the same way operands of an operation are checked, and leave a lambda with defaults directly in a `try` or `with` block alone, since a default may be there only to see it raise.

Refs #452.
B018 didn't report two kinds of statement that can never do anything:
Both were in the list of node types in #452. Like the operations from #579, a call in the part that runs right away (the first iterable of a generator expression, or a lambda's defaults) keeps it from being reported, and a generator expression or a lambda with defaults directly in a
try/withblock is left alone, since a test may build one to see that raise. f-strings stay out, as decided in #216.On CPython 3.12's
Lib(1,807 files) this adds 4 reports, all in interpreter tests that build these on purpose and already get B018 for bare constants. On Ubuntu's dist-packages (2,145 files) it adds 4: two are the bug above (UpdateManager/Core/utils.pyand its copy in DistUpgrade), two are a gettext marker trick inapt/auth.py.Tests are in
tests/eval_files/b018_generators_lambdas.py.b023.pyhas one bare lambda that now also gets B018, so I added that expectation. tox py310–py315 and pre-commit pass.