ruff PERF101
labelled unknown
Analysis Nemotron 3 Super
Flags redundant list() calls around iterables in for-loops.
Fix: Remove the list() constructor call while preserving its argument expression inside the for-loop header.
8 hazards
- Rewriting list(expr) to expr changes behavior if expr has side effects evaluated multiple times due to iteration, as list() evaluates expr once before looping.
- If expr is a generator, list() consumes it entirely while direct iteration may leave it partially consumed after loop break/continue.
- When expr is an iterator, list() creates a snapshot; removing it risks mutation during iteration affecting results.
- If expr returns a non-iterable, list() raises TypeError eagerly while direct iteration defers error until first iteration attempt.
- In nested loops, removing list() may alter scoping if expr uses variables modified in outer loop iterations.
- The fix drops any whitespace or formatting inside list() parentheses, potentially altering readability but not semantics.
- If expr contains a trailing comma making it a tuple, list() treats it as single argument while removal may misparse in rare edge cases (though unlikely).
- When expr is a call to a function returning self or list subclass, list() may copy while direct iteration uses original, affecting identity checks.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Whitespace normalization | Rule removes internal whitespace/formatting in list(), though this does not affect execution. |
| r1-p002 | silent | Runtime type annotation evaluation | list() call inside annotation context triggers NameError if undefined; removing it changes timing/scope. |
| r1-p003 | silent prediction off | Path behavior differences | list(os.path.iterdir()) vs direct iteration when dealing with broken symlinks or missing dirs. |
| r1-p004 | silent prediction off | Exception handling interaction | Removing list() changes when StopIteration is raised during iteration, affecting else/finally. |
| r1-p005 | silent prediction off | Generator consumption difference | list() exhausts generator immediately; direct iteration allows early break leaving state. |
| r1-p006 | silent prediction off | Identity with self-returning callable | list() creates copy; direct iteration uses original, affecting 'is' checks on elements. |
| r1-p007 | silent prediction off | Attribute descriptor side effects | Accessing .attr triggers __get__; list() calls it once, direct iteration may call multiple times. |
| r1-p008 | silent | Async generator interaction | list() on async generator invalid without await; direct iteration requires async for. list() cannot be used here, so rule should not fire -- testing near-miss. |
| r2-p001 | silent prediction off | Context manager non-file object | context managers that are not files, objects with write() but no close() |
| r2-p002 | invalid | Bytes vs str encoding newline | bytes vs str, encoding and newline handling, text vs binary modes |
| r2-p003 | silent prediction off | Mutable default arguments aliasing | mutable default arguments and aliasing after a rewrite |
| r2-p004 | silent prediction off | Function returning self or list subclass | When expr is a call to a function returning self or list subclass, list() may copy while direct iteration uses original, affecting identity checks. |
| r2-p005 | silent prediction off | Iterator mutation during iteration | When expr is an iterator, list() creates a snapshot; removing it risks mutation during iteration affecting results. |
| r2-p006 | silent prediction off | Class attributes descriptors side effects | class attributes vs instance attributes, descriptors, properties with side effects |
| r2-p007 | silent | Type annotations evaluated at runtime | type annotations evaluated at runtime, typing-only imports |
| r2-p008 | silent prediction off | Dict ordering duplicate keys hash eq | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
Sources
r1-p001 · Whitespace normalization
1for _ in list( range ( 5 ) ):
2 print(_)
0 1 2 3 4
r1-p002 · Runtime type annotation evaluation
1def f(): pass
2T = list[int]
3for x in T:
4 print(x)
*list[int]
r1-p003 · Path behavior differences
1import os
2os.makedirs('tmp', exist_ok=True)
3with open('tmp/file', 'w') as f: f.write('x')
4os.symlink('tmp/file', 'tmp/link')
5os.unlink('tmp/file')
6try:
7 for p in list(os.listdir('tmp')):
8 print('list:', p)
9except Exception as e:
10 print(type(e).__name__, e)
11finally:
12 import shutil
13 shutil.rmtree('tmp', ignore_errors=True)
list: link
r1-p004 · Exception handling interaction
1def gen():
2 yield 1
3 raise ValueError('bad')
4g = gen()
5try:
6 for x in list(g):
7 print(x)
8 print('else')
9except ValueError as e:
10 print('caught:', e)
11else:
12 print('no except')
13finally:
14 print('finally')
caught: bad finally
r1-p005 · Generator consumption difference
1def gen():
2 for i in range(5):
3 yield i
4g = gen()
5for x in list(g):
6 if x == 2:
7 break
8 print('loop:', x)
9print('remaining:', list(g))
loop: 0 loop: 1 remaining: []
r1-p006 · Identity with self-returning callable
1class ListLike(list):
2 def __iter__(self):
3 return super().__iter__()
4L = ListLike([1,2])
5for x in list(L):
6 print('copy:', x is L[0])
7for x in L:
8 print('direct:', x is L[0])
copy: True copy: False direct: True direct: False
r1-p007 · Attribute descriptor side effects
1class Attr:
2 def __get__(self, obj, owner):
3 print('get')
4 return [1,2]
5class Obj:
6 attr = Attr()
7O = Obj()
8for x in list(O.attr):
9 print('list iter:', x)
10for x in O.attr:
11 print('direct iter:', x)
get list iter: 1 list iter: 2 get direct iter: 1 direct iter: 2
r1-p008 · Async generator interaction
1async def agen():
2 yield 1
3 yield 2
4async def main():
5 try:
6 for x in list(agen()):
7 print(x)
8 except TypeError as e:
9 print('expected:', type(e).__name__, e)
10import asyncio
11asyncio.run(main())
expected: TypeError 'async_generator' object is not iterable
r2-p001 · Context manager non-file object
1class Ctx:
2 def __enter__(self):
3 return self
4 def __exit__(self, *exc):
5 pass
6 def write(self, x):
7 self.last = x
8ctx = Ctx()
9s = []
10for x in list(ctx):
11 ctx.write(x)
12 s.append(('written', ctx.last))
13print(s)
r2-p002 · Bytes vs str encoding newline (generated program does not parse: Traceback (most recent call last):)
1data = b'a
2b\r
3c
4'
5lines = []
6for line in list(data):
7 lines.append(chr(line))
8print(''.join(lines))
r2-p003 · Mutable default arguments aliasing
1def f(lst=[]):
2 lst.append(1)
3 return lst
4g = []
5for x in list(f()):
6 g.append(x)
7print(len(g), g)
1 [1]
r2-p004 · Function returning self or list subclass
1class MyList(list):
2 pass
3def get_self():
4 return MyList([1, 2])
5lst = get_self()
6for x in list(lst):
7 print(x is lst[0])
True False
r2-p005 · Iterator mutation during iteration
1def gen():
2 yield 1
3 yield 2
4 yield 3
5it = gen()
6out = []
7for x in list(it):
8 out.append(x)
9 try:
10 next(it)
11 except StopIteration:
12 pass
13print(out)
[1, 2, 3]
r2-p006 · Class attributes descriptors side effects
1class Desc:
2 def __get__(self, obj, objtype):
3 self.calls += 1
4 return [self.calls]
5 def __init__(self):
6 self.calls = 0
7class Obj:
8 attr = Desc()
9obj = Obj()
10results = []
11for x in list(obj.attr):
12 results.append(x)
13print(results)
[1]
r2-p007 · Type annotations evaluated at runtime
1from typing import List
2L: List[int] = [1, 2]
3for x in L:
4 print(x)
1 2
r2-p008 · Dict ordering duplicate keys hash eq
1class BadHash:
2 def __init__(self, val):
3 self.val = val
4 def __hash__(self):
5 return 0
6 def __eq__(self, other):
7 return isinstance(other, BadHash) and self.val == other.val
8d = {BadHash(1): 'a', BadHash(2): 'b'}
9for k in list(d):
10 print(k.val)
1 2