ruff PERF102
labelled unknown
Analysis Nemotron 3 Super
Flags dict.items() iteration where either loop variable is unused.
Fix: Replace dict.items() with dict.keys() when only the key is used, or dict.values() when only the value is used, preserving the used loop variable and loop body.
8 hazards
- Rewriting 'for k, v in d.items(): ...' to 'for k in d.keys(): ...' changes behavior if 'v' is referenced later in the function due to lingering binding.
- If the loop variable prefixed with '_' is actually used (e.g., via locals() or eval), replacing .items() with .values() removes the key binding, breaking access.
- When the dictionary is a non-standard mapping (e.g., a custom class without .keys()/.values()), the rewrite causes AttributeError.
- If .items(), .keys(), or .values() are overridden with custom logic (e.g., logging or filtering), the rewrite alters side effects.
- In nested scopes, replacing .items() with .keys()/.values() may inadvertently capture outer-scope variables if the loop variable name shadows them.
- If the loop body contains 'del v' or similar, removing the binding via .values() causes UnboundLocalError.
- The rewrite may disrupt debugging or tracing tools that rely on the frame locals containing both key and value variables.
- If the dictionary iteration is modified during iteration (e.g., adding/removing keys), .items(), .keys(), and .values() behave differently under mutation, altering which items are visited.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | Outer scope shadowing | In nested scopes, replacing .items() with .keys()/.values() may inadvertently capture outer-scope variables if the loop variable name shadows them. |
| r1-p002 | fired, no fix | Comments and docstrings | comments and docstrings attached to the removed or moved code |
| r1-p003 | fired, no fix | Type annotations evaluated at runtime | type annotations evaluated at runtime, typing-only imports |
| r1-p004 | fired, no fix | Mutable default arguments and aliasing | mutable default arguments and aliasing after a rewrite |
| r1-p005 | silent prediction off | Debugging/tracing tools | The rewrite may disrupt debugging or tracing tools that rely on the frame locals containing both key and value variables. |
| r1-p006 | fired, no fix | Parameter kinds and defaults | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r1-p007 | silent prediction off | Iterators consumed once | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r1-p008 | fired, no fix | Bytes vs str encoding | bytes vs str, encoding and newline handling, text vs binary modes |
| r2-p001 | fired, no fix | Non-standard mapping lacks .keys()/.values() | When the dictionary is a non-standard mapping (e.g., a custom class without .keys()/.values()), the rewrite causes AttributeError. |
| r2-p002 | silent prediction off | Mutation during iteration changes visited items | If the dictionary iteration is modified during iteration (e.g., adding/removing keys), .items(), .keys(), and .values() behave differently under mutation, altering which items are visited. |
| r2-p003 | silent prediction off | File mode truncation with partial write | file modes and truncation, partial writes, non-str data passed to write |
| r2-p004 | fired, no fix | del v in loop body causes UnboundLocalError after rewrite | If the loop body contains 'del v' or similar, removing the binding via .values() causes UnboundLocalError. |
| r2-p005 | silent prediction off | Overridden .items/.keys/.values with side effects | If .items(), .keys(), or .values() are overridden with custom logic (e.g., logging or filtering), the rewrite alters side effects. |
| r2-p006 | fired, no fix | Lingering binding of unused variable after rewrite | Rewriting 'for k, v in d.items(): ...' to 'for k in d.keys(): ...' changes behavior if 'v' is referenced later in the function due to lingering binding. |
| r2-p007 | silent prediction off | Frame locals missing value variable breaks debugger | The rewrite may disrupt debugging or tracing tools that rely on the frame locals containing both key and value variables. |
| r2-p008 | fired, no fix | Bytes vs str encoding mismatch in write | bytes vs str, encoding and newline handling, text vs binary modes |
Sources
r1-p001 · Outer scope shadowing
1x = 'outer'
2d = {'a': 1}
3for x, _ in d.items():
4 def inner():
5 return x
6 print(inner(), end='')
a
r1-p002 · Comments and docstrings
1d = {'k': 42}
2# comment before
3for key, _ in d.items(): # inline
4 """docstring"""
5 print(key)
k
r1-p003 · Type annotations evaluated at runtime
1from typing import List
2def f() -> List[int]:
3 d = {1: 10}
4 for k, _ in d.items():
5 return [k]
6print(f())
[1]
r1-p004 · Mutable default arguments and aliasing
1def g(d={}):
2 for k, _ in d.items():
3 d[k] = 0
4 return d
5print(g({1: 2}))
6print(g({}))
{1: 0}
{}r1-p005 · Debugging/tracing tools
1import sys
2d = {'a': 1}
3for k, v in d.items():
4 frame = sys._getframe(0)
5 print('k' in frame.f_locals and 'v' in frame.f_locals, end='')
True
r1-p006 · Parameter kinds and defaults
1def h(*, kw, d={'x': 1}):
2 for k, _ in d.items():
3 kw.append(k)
4 return kw
5print(h(kw=[]))
['x']
r1-p007 · Iterators consumed once
1def gen():
2 yield ('a', 1)
3 yield ('b', 2)
4g = gen()
5for k, _ in g:
6 print(k, end='')
7try:
8 next(g)
9 print('extra', end='')
10except StopIteration:
11 print('done', end='')
abdone
r1-p008 · Bytes vs str encoding
1with open('tmp.bin', 'wb') as f:
2 f.write(b'hello\nworld')
3with open('tmp.bin', 'rb') as f:
4 lines = {i: line for i, line in enumerate(f)}
5 for idx, _ in lines.items():
6 print(idx, end='')
01
r2-p001 · Non-standard mapping lacks .keys()/.values()
1class Map:
2 def __iter__(self):
3 return iter([('a', 1), ('b', 2)])
4 def items(self):
5 return list(self.__iter__())
6d = Map()
7try:
8 for k, v in d.items():
9 print(k)
10except AttributeError as e:
11 print(f'{type(e).__name__}: {e}')
a b
r2-p002 · Mutation during iteration changes visited items
1d = {'a': 1, 'b': 2}
2seen = []
3for k, v in d.items():
4 seen.append((k, v))
5 if k == 'a':
6 d['c'] = 3
7print(sorted(seen))
r2-p003 · File mode truncation with partial write
1with open('test.txt', 'w') as f:
2 pass
3with open('test.txt', 'r+') as f:
4 for k, v in {'a': 'hello', 'b': 'world'}.items():
5 f.write(v)
6 if k == 'a':
7 f.truncate()
8with open('test.txt', 'r') as f:
9 print(repr(f.read()))
10import os
11os.remove('test.txt')
'helloworld'
r2-p004 · del v in loop body causes UnboundLocalError after rewrite
1d = {'a': 1, 'b': 2}
2for k, v in d.items():
3 print(v)
4 del v
5print('done')
1 2 done
r2-p005 · Overridden .items/.keys/.values with side effects
1log = []
2class D(dict):
3 def items(self):
4 log.append('items')
5 return super().items()
6 def keys(self):
7 log.append('keys')
8 return super().keys()
9d = D({'a': 1})
10for k, v in d.items():
11 pass
12print(''.join(log))
items
r2-p006 · Lingering binding of unused variable after rewrite
1def f():
2 d = {'a': 1}
3 for k, v in d.items():
4 pass
5 try:
6 print(v)
7 except NameError as e:
8 return f'{type(e).__name__}: {e}'
9print(f())
1 None
r2-p007 · Frame locals missing value variable breaks debugger
1import sys
2def trace(frame, event, arg):
3 if event == 'line' and frame.f_locals.get('k') == 'a':
4 if 'v' in frame.f_locals:
5 print(f'v={frame.f_locals["v"]}')
6 else:
7 print("v missing")
8 return trace
9d = {'a': 1}
10sys.settrace(trace)
11for k, v in d.items():
12 pass
13sys.settrace(None)
r2-p008 · Bytes vs str encoding mismatch in write
1with open('test.bin', 'wb') as f:
2 for k, v in {b'a': b'hello', b'b': b'world'}.items():
3 f.write(v)
4with open('test.bin', 'rb') as f:
5 print(f.read())
6import nosuchmodule # This line is invalid but we catch it
b'helloworld'