ruff RUF017
labelled unknown
Analysis Nemotron 3 Super
Flags calls to sum() used to flatten lists of lists by providing [] as the start argument.
Fix: Rewrites sum(lists, []) as [*sublist for sublist in lists] on Python 3.15+, otherwise as functools.reduce(operator.iadd, lists, []) after importing functools and operator if needed.
10 hazards
- The rewrite accepts any iterable of iterables, whereas sum required actual lists, potentially changing behavior if non-list iterables are passed.
- If the original sum call appeared in a context where its TypeError on non-list inputs was being caught and handled, the rewrite may silently succeed and alter control flow.
- The fix may reorder evaluation: sum evaluates left-to-right with immediate accumulation, while the reduce form also evaluates left-to-right but via operator.iadd calls, which could differ if list concatenation had side effects (though unlikely for built-in lists).
- On Python 3.15+, the starred form [*sublist for sublist in lists] creates the list via a comprehension, which may have different performance characteristics but identical results for list inputs.
- If the variable name 'lists' is reused or shadowed in the surrounding scope, the fix’s comprehension or reduce expression could capture a different binding than the original sum call intended.
- The fix may drop inline or trailing comments attached to the sum call line, as the rewrite replaces the entire expression and may not preserve comment tokens.
- If the sum call was part of a larger expression (e.g., inside a function argument or conditional), the fix’s insertion of import statements (for reduce form) could affect module-level state or trigger side effects at import time.
- Using functools.reduce(operator.iadd, ...) assumes the elements support in-place addition via __iadd__; for lists this works, but if the iterables were custom sequences with unusual __iadd__ behavior, the result might diverge from sum’s repeated __add__ usage.
- In rare cases where the start value [] was not a literal but a variable, the fix incorrectly hardcodes [], altering behavior if that variable was mutated elsewhere or intended to be dynamic.
- If the code relied on the quadratic behavior of sum for timing or testing purposes, the rewrite changes performance characteristics, potentially breaking time-sensitive logic.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | Evaluation Order Side Effect | The fix may reorder evaluation: sum evaluates left-to-right with immediate accumulation, while the reduce form also evaluates left-to-right but via operator.iadd calls, which could differ if list conc |
| r1-p002 | fired, no fix | Star Imports and Module State | star-imports, module-level side effects, __all__ |
| r1-p003 | fired, no fix | Non-List Iterables | The rewrite accepts any iterable of iterables, whereas sum required actual lists, potentially changing behavior if non-list iterables are passed. |
| r1-p004 | silent prediction off | Custom __iadd__ vs __add__ | Using functools.reduce(operator.iadd, ...) assumes the elements support in-place addition via __iadd__; for lists this works, but if the iterables were custom sequences with unusual __iadd__ behavior, |
| r1-p005 | fired, no fix | Import-Time Side Effects in Expression | If the sum call was part of a larger expression (e.g., inside a function argument or conditional), the fix’s insertion of import statements (for reduce form) could affect module-level state or trigger |
| r1-p006 | fired, no fix | String Formatting Edge Cases | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
| r1-p007 | invalid | Keyword-Only and Args Forwarding | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r1-p008 | fired, no fix | Generator Consumption | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r2-p001 | fired, no fix | Type annotation evaluation with forward reference | type annotations evaluated at runtime, typing-only imports |
| r2-p002 | fired, no fix | F-string with escaped braces and quote nesting | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
| r2-p003 | silent | Binary file write with partial truncation | file modes and truncation, partial writes, non-str data passed to write |
| r2-p004 | fired, no fix | Variable shadowing of 'lists' in nested scope | If the variable name 'lists' is reused or shadowed in the surrounding scope, the fix’s comprehension or reduce expression could capture a different binding than the original sum call intended. |
| r2-p005 | fired, no fix | Trailing comment on sum call line lost in fix | The fix may drop inline or trailing comments attached to the sum call line, as the rewrite replaces the entire expression and may not preserve comment tokens. |
| r2-p006 | silent prediction off | Custom sequence with __iadd__ vs __add__ divergence | Using functools.reduce(operator.iadd, ...) assumes the elements support in-place addition via __iadd__; for lists this works, but if the iterables were custom sequences with unusual __iadd__ behavior, |
| r2-p007 | fired, no fix | Sum call inside function argument triggering module-level import | If the sum call was part of a larger expression (e.g., inside a function argument or conditional), the fix’s insertion of import statements (for reduce form) could affect module-level state or trigger |
| r2-p008 | silent prediction off | Bytes vs str in sum with binary mode interaction | bytes vs str, encoding and newline handling, text vs binary modes |
Sources
r1-p001 · Evaluation Order Side Effect
1class LogList(list):
2 def __init__(self, data, name):
3 super().__init__(data)
4 self.name = name
5 def __add__(self, other):
6 print(f'add {self.name} + {getattr(other, "name", "?")}')
7 return super().__add__(other)
8 def __iadd__(self, other):
9 print(f'iadd {self.name} += {getattr(other, "name", "?")}')
10 return super().__iadd__(other)
11
12lst1 = LogList([1], 'A')
13lst2 = LogList([2], 'B')
14lst3 = LogList([3], 'C')
15print('sum:')
16result = sum([lst1, lst2, lst3], [])
17print('reduce-like:')
18# Manual reduce for comparison
19from functools import reduce
20import operator
21result2 = reduce(operator.iadd, [lst1, lst2, lst3], [])
22print('Results equal:', result == result2)
sum: reduce-like: Results equal: True
r1-p002 · Star Imports and Module State
1# Simulate a module with side effects on import
2import sys
3if 'hazard_mod' not in sys.modules:
4 exec("""
5# hazard_mod.py
6_side_effect = []
7def register(x):
8 _side_effect.append(x)
9 return x
10__all__ = ['register']
11""", {'__name__': 'hazard_mod'})
12 sys.modules['hazard_mod'] = sys.modules.get('hazard_mod', type('hazard_mod', (), {}))
13 hazard_mod = sys.modules['hazard_mod']
14 hazard_mod._side_effect = []
15 hazard_mod.register = lambda x: (hazard_mod._side_effect.append(x), x)[1]
16 hazard_mod.__all__ = ['register']
17
18from hazard_mod import * # star-import
19
20lists = [[1], [2], [3]]
21print('Before sum:', hazard_mod._side_effect)
22result = sum(lists, [])
23print('After sum:', hazard_mod._side_effect)
24print('Result:', result)
Before sum: [] After sum: [] Result: [1, 2, 3]
r1-p003 · Non-List Iterables
1def gen_lists():
2 yield [1, 2]
3 yield (3, 4) # tuple, not list
4 yield {5, 6} # set, not list
5
6print('Input:')
7for item in gen_lists():
8 print(f' {item} ({type(item).__name__})')
9
10try:
11 result = sum(gen_lists(), [])
12 print('sum result:', result, type(result))
13except Exception as e:
14 print('sum error:', type(e).__name__, str(e))
15
16# Reset generator
17print('\nReduce form:')
18from functools import reduce
19import operator
20result2 = reduce(operator.iadd, gen_lists(), [])
21print('reduce result:', result2, type(result2))
Input:
[1, 2] (list)
(3, 4) (tuple)
{5, 6} (set)
sum error: TypeError can only concatenate list (not "tuple") to list
Reduce form:
reduce result: [1, 2, 3, 4, 5, 6] <class 'list'>r1-p004 · Custom __iadd__ vs __add__
1class WeirdSeq:
2 def __init__(self, data, label):
3 self.data = list(data)
4 self.label = label
5 def __add__(self, other):
6 # Always returns a fresh list, ignores self
7 return list(getattr(other, 'data', [])) if hasattr(other, 'data') else list(other)
8 def __iadd__(self, other):
9 # Mutates self to be a copy of other
10 self.data.clear()
11 self.data.extend(getattr(other, 'data', []) if hasattr(other, 'data') else other)
12 return self
13 def __iter__(self):
14 return iter(self.data)
15 def __repr__(self):
16 return f'WeirdSeq({self.data}, {self.label})'
17
18a = WeirdSeq([1, 2], 'a')
19b = WeirdSeq([3, 4], 'b')
20c = WeirdSeq([5, 6], 'c')
21
22print('Initial:', a, b, c)
23print('\nUsing sum:')
24try:
25 s = sum([a, b, c], WeirdSeq([], 'start'))
26 print('Result:', s)
27 print('a,b,c after:', a, b, c)
28except Exception as e:
29 print('Error:', type(e).__name__, e)
30
31# Reset
32a = WeirdSeq([1, 2], 'a')
33b = WeirdSeq([3, 4], 'b')
34c = WeirdSeq([5, 6], 'c')
35print('\nUsing reduce:')
36from functools import reduce
37import operator
38try:
39 r = reduce(operator.iadd, [a, b, c], WeirdSeq([], 'start'))
40 print('Result:', r)
41 print('a,b,c after:', a, b, c)
42except Exception as e:
43 print('Error:', type(e).__name__, e)
Initial: WeirdSeq([1, 2], a) WeirdSeq([3, 4], b) WeirdSeq([5, 6], c) Using sum: Error: TypeError can only concatenate list (not "WeirdSeq") to list Using reduce: Result: WeirdSeq([5, 6], start) a,b,c after: WeirdSeq([1, 2], a) WeirdSeq([3, 4], b) WeirdSeq([5, 6], c)
r1-p005 · Import-Time Side Effects in Expression
1import sys
2
3# Track if hazardous module is imported
4import_side_effects = []
5
6# Simulate importing a module with side effects
7if 'effect_mod' not in sys.modules:
8 mod = type('effect_mod', (), {})
9 mod._import_log = []
10 def init_side():
11 mod._import_log.append('initialized')
12 return 42
13 mod.CONSTANT = init_side()
14 sys.modules['effect_mod'] = mod
15
16effect_mod = sys.modules['effect_mod']
17
18def get_lists():
19 # Trigger import of effect_mod when called
20 _ = effect_mod.CONSTANT # access to trigger if not already
21 return [[1], [2]]
22
23print('Before call:', effect_mod._import_log)
24try:
25 # This sum call will trigger the fix -> add imports -> but we simulate
26 # that the fix would insert imports HERE, which could cause side effects
27 result = sum(get_lists(), [])
28 print('After sum:', effect_mod._import_log)
29 print('Result:', result)
30except Exception as e:
31 print('Error:', type(e).__name__, e)
Before call: ['initialized'] After sum: ['initialized'] Result: [1, 2]
r1-p006 · String Formatting Edge Cases
1lists = [['a', 'b'], ['c', 'd']]
2flattened = sum(lists, [])
3print('Flattened:', flattened)
4
5# Test various formatting
6s = ''.join(flattened)
7print('%% formatting:', '%s' % s)
8print('.format():', '{}'.format(s))
9print('f-string:', f'{s}')
10print('repr:', repr(s))
11
12# Nested quotes in f-string
13quote_test = f'He said "{''.join(flattened)}"'
14print('Nested quote f-string:', quote_test)
15
16# Format with special chars
17try:
18 formatted = '{!r:{}}'.format(s, 10)
19 print('Format spec:', formatted)
20except Exception as e:
21 print('Format error:', type(e).__name__, e)
Flattened: ['a', 'b', 'c', 'd'] %% formatting: abcd .format(): abcd f-string: abcd repr: 'abcd' Nested quote f-string: He said "abcd" Format spec: 'abcd'
r1-p007 · Keyword-Only and Args Forwarding (generated program does not parse: Traceback (most recent call last):)
1default_val = []
2def append_to_lists(*args, source=default_val, , **kwargs):
3 # Note: invalid syntax - fix below
4 pass
5
6# Correct version:
7def process_data(*arrays, source=None, , **opts):
8 if source is None:
9 source = []
10 flattened = sum(arrays, source)
11 opts.setdefault('processed', True)
12 return flattened, opts
13
14# Hazard: default_val mutable
15print('Initial default_val:', id(default_val))
16result1, opts1 = process_data([1, 2], [3, 4])
17print('After first call:', id(default_val), default_val)
18result2, opts2 = process_data([5, 6])
19print('After second call:', id(default_val), default_val)
20print('Results:', result1, result2)
21print('Opts:', opts1, opts2)
r1-p008 · Generator Consumption
1def make_gen():
2 print(' Generator started')
3 for i in range(3):
4 print(f' Yielding [{i}, {i+1}]')
5 yield [i, i+1]
6 print(' Generator ended')
7
8print('First sum:')
9gen = make_gen()
10result1 = sum(gen, [])
11print('Result:', result1)
12
13print('\nSecond sum (same generator):')
14gen2 = make_gen() # new generator
15result2 = sum(gen2, [])
16print('Result:', result2)
17
18print('\nManual reduce for comparison:')
19from functools import reduce
20import operator
21gen3 = make_gen()
22result3 = reduce(operator.iadd, gen3, [])
23print('Reduce result:', result3)
First sum: Generator started Yielding [0, 1] Yielding [1, 2] Yielding [2, 3] Generator ended Result: [0, 1, 1, 2, 2, 3] Second sum (same generator): Generator started Yielding [0, 1] Yielding [1, 2] Yielding [2, 3] Generator ended Result: [0, 1, 1, 2, 2, 3] Manual reduce for comparison: Generator started Yielding [0, 1] Yielding [1, 2] Yielding [2, 3] Generator ended Reduce result: [0, 1, 1, 2, 2, 3]
r2-p001 · Type annotation evaluation with forward reference
1from __future__ import annotations
2
3def process_lists(lists):
4 return sum(lists, []) # flatten list of int pairs
5
6pairs: list[tuple[int, int]] = [(1, 2), (3, 4)]
7result = process_lists(pairs)
8print(result)
r2-p002 · F-string with escaped braces and quote nesting
1data = [[1], [2, 3]]
2flattened = sum(data, []) # {{ flattened }}
3print(f"Result: {flattened}")
Result: [1, 2, 3]
r2-p003 · Binary file write with partial truncation
1import os
2if os.path.exists('test.bin'):
3 os.remove('test.bin')
4with open('test.bin', 'wb') as f:
5 chunks = [b'hello', b' ', b'world']
6 written = sum(chunks, b'') # flatten byte chunks
7 f.write(written)
8with open('test.bin', 'rb') as f:
9 print(f.read())
10if os.path.exists('test.bin'):
11 os.remove('test.bin')
r2-p004 · Variable shadowing of 'lists' in nested scope
1lists = [[1, 2], [3, 4]]
2result1 = sum(lists, []) # outer lists
3for lists in [[5, 6]]: # shadows outer lists
4 result2 = sum(lists, []) # inner lists
5print(result1, result2)
r2-p005 · Trailing comment on sum call line lost in fix
1data = [[1, 2], [3]]
2flat = sum(data, []) # flatten for printing
3print(flat) # should be [1,2,3]
[1, 2, 3]
r2-p006 · Custom sequence with __iadd__ vs __add__ divergence
1class Vec:
2 def __init__(self, x):
3 self.x = x
4 def __add__(self, other):
5 return Vec(self.x + other.x)
6 def __iadd__(self, other):
7 self.x += other.x * 2 # different behavior!
8 return self
9 def __repr__(self):
10 return f'Vec({self.x})'
11
12items = [Vec(1), Vec(2)]
13start = Vec(0)
14result_sum = sum(items, start) # uses __add__
15# After fix: reduce(iadd, items, start) uses __iadd__
16print('sum result:', result_sum)
17print('start after sum:', start) # start mutated if iadd used
sum result: Vec(3) start after sum: Vec(0)
r2-p007 · Sum call inside function argument triggering module-level import
1def consume(x):
2 print('consumed:', x)
3
4# Trigger functools.reduce import via sum fix
5consume(sum([[1], [2]], [])) # should print [1, 2]
6print('done')
consumed: [1, 2] done
r2-p008 · Bytes vs str in sum with binary mode interaction
1chunks = [b'ab', b'cd']
2result = sum(chunks, b'') # bytes concatenation
3print('bytes result:', result)
4try:
5 sum([[1, 2]], '') # mixing list and str start - should fail
6except TypeError as e:
7 print('error:', type(e).__name__, str(e))