# ruff PLR1733: fix changes program behavior

Internal evidence sheet generated by fixhunt. Not an issue text. A human re-verifies, checks the duplicates
below, and writes the upstream report in their own words following the project's AI policy.

- Tool: ruff ruff 0.17.1
- Rule: PLR1733 (tool labels the fix: unknown, this fix reported as safe)
- Oracle: fix-changes-behavior
- Judge (Nemotron 3 Ultra, advisory): real, severity 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.
- Program: minimized from 14 to 11 lines

## Reproducer

```python
class Mapping:
    def __init__(self):
        self.data = {'a': 1}
        self.log = []
    def __getitem__(self, k):
        self.log.append(k)
    def items(self):
        return self.data.items()
m = Mapping()
for k, v in m.items():
    print(m[k])  # triggers __getitem__, logs 'a'
```

## Commands

```sh
python3 prog.py
ruff check --preview --isolated --select PLR1733 --fix prog.py
python3 prog.py
```

## Observed

```
stdout before:
None
stdout after:
1
```

## Diff applied by the fix

```diff
--- orig.py	2026-10-22 13:35:05.557674074 -0400
+++ fixed.py	2026-10-22 13:35:08.181718331 -0400
@@ -8,4 +8,4 @@
         return self.data.items()
 m = Mapping()
 for k, v in m.items():
-    print(m[k])  # triggers __getitem__, logs 'a'
+    print(v)  # triggers __getitem__, logs 'a'
```

## Why the rewrite is not equivalent (model draft, verify before use)

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.

## Upstream search

Queries: "PLR1733 fix", "PLR1733 avoid redundant mapping access", "PLR1733 use unpacked value instead of key lookup", "PLR1733 replace dict[key] with value in iteration"

- related: https://github.com/astral-sh/ruff/issues/25182
  same rule PLR1733 but different construct (else-clause dict access vs custom mapping iteration)
- related: https://github.com/astral-sh/ruff/issues/29422
  same rule PLR1733 mentioned among other fix bugs but no detail on this construct
- related: https://github.com/astral-sh/ruff/issues/25150
  same rule PLR1733 false positive but on for-else dictionary access, not custom mapping iteration

<details><summary>All hits</summary>

- [github, open] Additional false positives in `PLR1733` and `PLR1736` — https://github.com/astral-sh/ruff/issues/25182
- [github, open] A few more: missed reports vs flake8/pylint, plus two fix bugs (B012, F402, F823, SIM102, SIM401, PLW1641, PLR1736, PLW0406, N801, UP034) — https://github.com/astral-sh/ruff/issues/29422
- [github, closed] unused-function-argument not silenced by prefixed underscore if variable name is "次" — https://github.com/astral-sh/ruff/issues/28721
- [github, open] Implement Pylint — https://github.com/astral-sh/ruff/issues/970
- [github, closed] PLR1733 false positive — https://github.com/astral-sh/ruff/issues/25150
- [github, closed] Pack of fuzzer Autofix/Infinite loop errors [2] — https://github.com/astral-sh/ruff/issues/8402
- [tavily] [syntax-errors] Detect duplicate keys in `match` mapping patterns by ntBre · Pull Request #17129 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/pull/17129
- [tavily] Avoid panic in unfixable `redundant-numeric-union` by charliermarsh · Pull Request #14402 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/pull/14402
- [tavily] [ty] Add assert_never fixes for redundant final elif branches by AlexWaygood · Pull Request #28178 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/pull/28178
- [tavily] [`flake8-pyi`] Implement `redundant-none-literal` (`PYI061`) by sbrugman · Pull Request #14316 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/pull/14316
- [tavily] Automatically remove redundant open modes · Issue #640 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/640
- [tavily] Validation for ruff config file · Issue #10487 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/10487
- [tavily] SyntaxError in Python 3.11 when unpacking sequences inside of [] · Issue #2321 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/2321
- [tavily] New Linting Rule: Prefer `setdefault` Over Conditional Key Checks for Dictionary Assignment · Issue #7897 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/7897
- [tavily] Use str.casefold() instead of str.lower() for comparison. · Issue #7728 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/7728
- [tavily] [ty] Fix variance of frozen dataclass-transform models by sharkdp · Pull Request #23931 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/pull/23931

</details>
