ruff PLR1733: fix changes program behavior
Hypothesis Nemotron 3 Super predicted to fire fired 1×
When d is not a standard dict but a custom mapping with __getitem__ side effects (e.g., logging, state changes), replacing d[k] with v omits those effects.
Program
Output
None
1
- fired
- 1 before · 0 after
- runs
- each version twice, same file name, no network
Judge Nemotron 3 Ultra advisory real · high
The rule assumes the iterated object is a plain dict where m[k] is a pure lookup with no side effects. Here m is a custom Mapping whose __getitem__ logs each access; the fix replaces the logged lookup with the already-available value v, silently dropping the log entry and changing the observable behavior.
Explanation Nemotron 3 Super unverified
The original loop iterates over key-value pairs from `m.items()`, then prints `m[k]`, which calls `__getitem__` with each key `'a'`. Since `self.data['a']` is defined as `1`, this should print `1`, but the observed output shows `None`, indicating that `__getitem__` likely returns `None` when accessed via `m[k]` despite logging the key—perhaps due to missing or incorrect return logic in `__getitem__`. However, the fix replaces `m[k]` with `v`, which is the value directly yielded by `items()` (i.e., `1`), bypassing `__getitem__` entirely. Thus, the fix changes behavior because it avoids the side-effect-prone `__getitem__` call, and the original method does not return the expected value, making the rewrite unsafe despite being labeled as such. The label "safe" is incorrect here because the semantic dependency on `__getitem__`’s return value is violated.
Reproduce
- file
prog.py(the before program)- minimized program
- r2-p003.py
- evidence sheet
- r2-p003.md
- regression fixture
- r2-p003.py the minimized program with a header stating the observed change, for the tool's test suite
python3 prog.py ruff check --preview --isolated --select PLR1733 --fix prog.py python3 prog.py
Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra
PLR1733 fix · PLR1733 avoid redundant mapping access · PLR1733 use unpacked value instead of key lookup · PLR1733 replace dict[key] with value in iteration
- related astral-sh/ruff/issues/25182 same rule PLR1733 but different construct (else-clause dict access vs custom mapping iteration)
- related astral-sh/ruff/issues/29422 same rule PLR1733 mentioned among other fix bugs but no detail on this construct
- related astral-sh/ruff/issues/25150 same rule PLR1733 false positive but on for-else dictionary access, not custom mapping iteration
Details
- program
- Custom mapping with __getitem__ side effects
- hazard
- When d is not a standard dict but a custom mapping with __getitem__ side effects (e.g., logging, state changes), replacing d[k] with v omits those effects.
- oracle
stdout before: None stdout after: 1- run
- 2026-10-17T21-02-04-500Z-1p30-ruff-PLR1733
generated program (14 lines) and the fix's diff
--- orig.py 2026-10-17 17:02:26.780251901 -0400 +++ fixed.py 2026-10-17 17:02:26.944253843 -0400 @@ -10,5 +10,5 @@ m = Mapping() for k, v in m.items(): - print(m[k]) # triggers __getitem__, logs 'a' + print(v) # triggers __getitem__, logs 'a' print('log:', m.log)
rule documentation given to the models
# unnecessary-dict-index-lookup (PLR1733)
Derived from the **Pylint** linter.
Fix is always available.
## What it does
Checks for key-based dict accesses during `.items()` iterations.
## Why is this bad?
When iterating over a dict via `.items()`, the current value is already
available alongside its key. Using the key to look up the value is
unnecessary.
## Example
```python
FRUITS = {"apple": 1, "orange": 10, "berry": 22}
for fruit_name, fruit_count in FRUITS.items():
print(FRUITS[fruit_name])
```
Use instead:
```python
FRUITS = {"apple": 1, "orange": 10, "berry": 22}
for fruit_name, fruit_count in FRUITS.items():
print(fruit_count)
```