ruff PLR1733: fix changes program behavior
Hypothesis Nemotron 3 Super predicted to fire fired 1×
d[k] invokes logging __getitem__; replacing with v omits the side effect.
Program
Output
GET a
(no stdout)
- fired
- 1 before · 0 after
- runs
- each version twice, same file name, no network
Judge Nemotron 3 Ultra advisory real · medium
The program uses a dict subclass that overrides __getitem__ to log each access. The rule's fix replaces d[k] with the loop variable v, which bypasses the overridden method and eliminates the log output. The documentation presents the rewrite as equivalent for ordinary dicts but does not account for subclasses with side effects in __getitem__.
Explanation Nemotron 3 Super unverified
The original loop accesses each value via `d[k]`, which invokes `__getitem__` and prints 'GET a'. The fix replaces this with `_ = v`, using the already retrieved value from iteration without re-accessing the dictionary, so `__getitem__` is never called and nothing is printed. This changes observable behavior because the side effect of logging depends on dictionary access, which the fix eliminates. Thus, the rewrite is not semantics‑preserving—it removes a required side effect even though the variable `v` holds the same value as `d[k]` would return. The fix incorrectly assumes that avoiding re‑access preserves meaning, ignoring that the access itself performs work beyond value retrieval.
Reproduce
- file
prog.py(the before program)- minimized program
- r1-p006.py
- evidence sheet
- r1-p006.md
- regression fixture
- r1-p006.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 dict items iteration redundant access · ruff PLR1733 unnecessary dictionary lookup during items loop · dict.__getitem__ called on already retrieved value from items()
- related astral-sh/ruff/issues/25182 same rule PLR1733 but different construct (else-clause access vs loop-body redundant indexing)
- related astral-sh/ruff/issues/29422 same rule PLR1733 mentioned among other fix bugs but no snippet shows this exact redundant-indexing pattern
- related astral-sh/ruff/issues/25150 same rule PLR1733 but different construct (else-clause false positive vs loop-body redundant indexing)
Details
- program
- Custom Mapping __getitem__ Side Effects
- hazard
- d[k] invokes logging __getitem__; replacing with v omits the side effect.
- oracle
stdout before: GET a stdout after:- run
- 2026-10-17T21-02-04-500Z-1p30-ruff-PLR1733
generated program (10 lines) and the fix's diff
--- orig.py 2026-10-17 17:02:19.488165407 -0400 +++ fixed.py 2026-10-17 17:02:19.650167331 -0400 @@ -6,5 +6,5 @@ trace = [] for k, v in d.items(): trace.append(v) - _ = d[k] # triggers PLR1733: logs again; fix would omit log + _ = v # triggers PLR1733: logs again; fix would omit log print('trace:', trace)
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)
```