ruff B010
labelled unknown
Analysis Nemotron 3 Super
Flags setattr() calls where the attribute name argument is a literal string.
Fix: Rewrites setattr(obj, "attr", value) to obj.attr = value, preserving the target object, attribute name literal, and value expression exactly.
11 hazards
- Rewriting setattr(obj, "ſ", 1) to obj.ſ = 1 changes behavior because Python normalizes identifiers in attribute access via NFKC, turning "ſ" into "s".
- If the setattr call contains inline comments (e.g., setattr(x, "a", 1 # comment)), the autofix may drop them since the rewritten assignment cannot preserve arbitrary comment placement.
- When the target object is a complex expression like setattr(f()[0], "a", 1), rewriting to f()[0].a = 1 may change evaluation order or side effects if f() has mutable state.
- If the attribute name literal is not a valid Python identifier (e.g., setattr(obj, "123", 1)), the rewrite obj.123 = 1 is a syntax error.
- Rewriting setattr(obj, "__builtins__", value) to obj.__builtins__ = value may violate module-level immutability expectations or trigger unintended name mangling in classes.
- In code relying on dynamic attribute names via setattr, replacing with direct assignment may break frameworks that intercept setattr via __setattr__ but not direct attribute assignment.
- If the value expression contains side effects (e.g., setattr(obj, "a", print("side") or 1)), moving it into an assignment does not change evaluation timing, but refactoring tools might reorder expressions unsafely in broader contexts.
- When used in nested scopes like setattr(lambda: obj, "a", 1), the rewrite (lambda: obj).a = 1 may fail if the lambda’s return value is not assignable due to expression context restrictions.
- Rewriting setattr(klass, "ClsAttr", 42) to klass.ClsAttr = 42 inside a function may create an attribute on the class object at runtime where the original intended a local or closure variable due to mis-scoping.
- If the target object is a property returning a non-setattr-compatible object (e.g., a read-only proxy), the assignment may succeed where setattr would have raised an AttributeError due to __setattr__ overrides.
- In multi-target assignments like setattr(a, "x", setattr(b, "y", 1)), the autofix may produce invalid syntax or altered semantics due to broken expression chaining.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Exception handling after rewrite | Exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r1-p002 | fix ok | Evaluation order with side-effecting target | When the target object is a complex expression like setattr(f()[0], "a", 1), rewriting to f()[0].a = 1 may change evaluation order or side effects if f() has mutable state. |
| r1-p003 | fix ok | Mis-scoped attribute creation on class | Rewriting setattr(klass, "ClsAttr", 42) to klass.ClsAttr = 42 inside a function may create an attribute on the class object at runtime where the original intended a local or closure variable due to mi |
| r1-p004 | fix ok | Builtins override via attribute assignment | Rewriting setattr(obj, "__builtins__", value) to obj.__builtins__ = value may violate module-level immutability expectations or trigger unintended name mangling in classes. |
| r1-p005 | fix ok | Value expression with side effects | If the value expression contains side effects (e.g., setattr(obj, "a", print("side") or 1)), moving it into an assignment does not change evaluation timing, but refactoring tools might reorder express |
| r1-p006 | silent prediction off | Chained setattr in assignment | In multi-target assignments like setattr(a, "x", setattr(b, "y", 1)), the autofix may produce invalid syntax or altered semantics due to broken expression chaining. |
| r1-p007 | fix ok | File write truncation and partial writes | file modes and truncation, partial writes, non-str data passed to write |
| r1-p008 | fix ok | Truthiness and equality distinctions | truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' |
| r2-p001 | fix ok | Property returns read-only proxy blocking assignment | If the target object is a property returning a non-setattr-compatible object (e.g., a read-only proxy), the assignment may succeed where setattr would have raised an AttributeError due to __setattr__ |
| r2-p002 | fix ok | Binary mode file write with str vs bytes confusion | bytes vs str, encoding and newline handling, text vs binary modes |
| r2-p003 | fix ok | Text mode file write truncation and partial write with newline translation | file modes and truncation, partial writes, non-str data passed to write |
| r2-p004 | fix ok | Float precision loss in assignment vs setattr context | int/float/bool coercion, Decimal, division and rounding |
| r2-p005 | fix ok | Star-import shadowing __all__ and triggering attribute write to module | star-imports, module-level side effects, __all__ |
| r2-p006 | fix ok | Mutable default argument aliasing after rewrite | mutable default arguments and aliasing after a rewrite |
| r2-p007 | fired, no fix | Inline comment lost during autofix rewrite | If the setattr call contains inline comments (e.g., setattr(x, "a", 1 # comment)), the autofix may drop them since the rewritten assignment cannot preserve arbitrary comment placement. |
| r2-p008 | silent prediction off | Side effect in value expression: print during setattr vs assignment | If the value expression contains side effects (e.g., setattr(obj, "a", print("side") or 1)), moving it into an assignment does not change evaluation timing, but refactoring tools might reorder express |
Sources
r1-p001 · Exception handling after rewrite
1def f():
2 try:
3 x = []
4 setattr(x, 'append', lambda v: (_ for _ in ()).throw(ValueError('bad')))
5 x.append(1)
6 except ValueError as e:
7 print('caught', e)
8 else:
9 print('no exc')
10 finally:
11 print('finally')
12f()
--- orig.py 2026-10-17 16:11:51.039178352 -0400 +++ fixed.py 2026-10-17 16:11:51.423168980 -0400 @@ -1,7 +1,7 @@ def f(): try: x = [] - setattr(x, 'append', lambda v: (_ for _ in ()).throw(ValueError('bad'))) + x.append = lambda v: (_ for _ in ()).throw(ValueError('bad')) x.append(1) except ValueError as e: print('caught', e)
finally
r1-p002 · Evaluation order with side-effecting target
1state = []
2def f():
3 state.append(len(state))
4 return [0, 0]
5setattr(f(), 'a', 99)
6print('state:', state)
--- orig.py 2026-10-17 16:11:51.623164099 -0400 +++ fixed.py 2026-10-17 16:11:52.026154265 -0400 @@ -2,5 +2,5 @@ def f(): state.append(len(state)) return [0, 0] -setattr(f(), 'a', 99) +f().a = 99 print('state:', state)
r1-p003 · Mis-scoped attribute creation on class
1def outer():
2 ClsAttr = 1
3 class C: pass
4 def inner():
5 setattr(C, 'ClsAttr', 42)
6 print('local ClsAttr:', ClsAttr)
7 print('C.ClsAttr:', C.ClsAttr)
8 inner()
9outer()
--- orig.py 2026-10-17 16:11:52.226149395 -0400 +++ fixed.py 2026-10-17 16:11:52.372145840 -0400 @@ -2,7 +2,7 @@ ClsAttr = 1 class C: pass def inner(): - setattr(C, 'ClsAttr', 42) + C.ClsAttr = 42 print('local ClsAttr:', ClsAttr) print('C.ClsAttr:', C.ClsAttr) inner()
local ClsAttr: 1 C.ClsAttr: 42
r1-p004 · Builtins override via attribute assignment
1class ModLike:
2 pass
3m = ModLike()
4setattr(m, '__builtins__', {'len': lambda x: 42})
5print('m.__builtins__["len"]([1,2,3]):', m.__builtins__['len']([1,2,3]))
--- orig.py 2026-10-17 16:11:52.452143892 -0400 +++ fixed.py 2026-10-17 16:11:52.612139996 -0400 @@ -1,5 +1,5 @@ class ModLike: pass m = ModLike() -setattr(m, '__builtins__', {'len': lambda x: 42}) +m.__builtins__ = {'len': lambda x: 42} print('m.__builtins__["len"]([1,2,3]):', m.__builtins__['len']([1,2,3]))
m.__builtins__["len"]([1,2,3]): 42
r1-p005 · Value expression with side effects
1log = []
2def side():
3 log.append('side')
4 return 1
5obj = type('O', (), {})()
6setattr(obj, 'a', side() or 0)
7print('log:', log)
8print('obj.a:', obj.a)
--- orig.py 2026-10-17 16:11:52.704137756 -0400 +++ fixed.py 2026-10-17 16:11:52.862133908 -0400 @@ -3,6 +3,6 @@ log.append('side') return 1 obj = type('O', (), {})() -setattr(obj, 'a', side() or 0) +obj.a = side() or 0 print('log:', log) print('obj.a:', obj.a)
log: ['side'] obj.a: 1
r1-p006 · Chained setattr in assignment
1class A: pass
2class B: pass
3a = A()
4b = B()
5result = setattr(a, 'x', setattr(b, 'y', 1))
6print('a.x:', a.x)
7print('b.y:', b.y)
8print('result:', result)
a.x: None b.y: 1 result: None
r1-p007 · File write truncation and partial writes
1with open('test.txt', 'w') as f:
2 setattr(f, 'write', lambda data: f.__write__(data.upper()))
3 f.write('hi')
4with open('test.txt') as f:
5 print(f.read())
6import os; os.remove('test.txt')
--- orig.py 2026-10-17 16:11:53.109127900 -0400 +++ fixed.py 2026-10-17 16:11:53.535117550 -0400 @@ -1,5 +1,5 @@ with open('test.txt', 'w') as f: - setattr(f, 'write', lambda data: f.__write__(data.upper())) + f.write = lambda data: f.__write__(data.upper()) f.write('hi') with open('test.txt') as f: print(f.read())
r1-p008 · Truthiness and equality distinctions
1class X:
2 def __bool__(self): return False
3 def __len__(self): return 1
4obj = type('O', (), {})()
5setattr(obj, 'flag', X())
6if obj.flag:
7 print('true')
8else:
9 print('false but len=', len(obj.flag))
10print('obj.flag is False:', obj.flag is False)
--- orig.py 2026-10-17 16:11:53.752112278 -0400 +++ fixed.py 2026-10-17 16:11:53.914108342 -0400 @@ -2,7 +2,7 @@ def __bool__(self): return False def __len__(self): return 1 obj = type('O', (), {})() -setattr(obj, 'flag', X()) +obj.flag = X() if obj.flag: print('true') else:
false but len= 1 obj.flag is False: False
r2-p001 · Property returns read-only proxy blocking assignment
1class ReadOnlyProxy:
2 def __setattr__(self, name, value):
3 raise AttributeError('read-only')
4
5class Obj:
6 @property
7 def p(self):
8 return ReadOnlyProxy()
9
10obj = Obj()
11try:
12 setattr(obj.p, 'x', 1)
13except AttributeError as e:
14 print('setattr error:', type(e).__name__, ':', e)
15else:
16 print('setattr succeeded')
17
18try:
19 obj.p.x = 1 # rewritten by autofix
20except AttributeError as e:
21 print('assignment error:', type(e).__name__, ':', e)
22else:
23 print('assignment succeeded')
--- orig.py 2026-10-17 16:12:15.983585807 -0400 +++ fixed.py 2026-10-17 16:12:16.143582116 -0400 @@ -9,7 +9,7 @@ obj = Obj() try: - setattr(obj.p, 'x', 1) + obj.p.x = 1 except AttributeError as e: print('setattr error:', type(e).__name__, ':', e) else:
setattr error: AttributeError : read-only assignment error: AttributeError : read-only
r2-p002 · Binary mode file write with str vs bytes confusion
1f = open('test.bin', 'wb')
2try:
3 setattr(f, 'mode', 'wb') # rule triggers
4 f.write(b'hello')
5 pos = f.tell()
6 print('position after write:', pos)
7except Exception as e:
8 print('error during setattr/write:', type(e).__name__, ':', e)
9f.close()
10
11f2 = open('test.bin', 'rb')
12print('file content:', f2.read())
13f2.close()
14import os
15os.remove('test.bin')
--- orig.py 2026-10-17 16:12:16.242579833 -0400 +++ fixed.py 2026-10-17 16:12:16.395576304 -0400 @@ -1,6 +1,6 @@ f = open('test.bin', 'wb') try: - setattr(f, 'mode', 'wb') # rule triggers + f.mode = 'wb' # rule triggers f.write(b'hello') pos = f.tell() print('position after write:', pos)
error during setattr/write: AttributeError : attribute 'mode' of '_io.BufferedWriter' objects is not writable file content: b''
r2-p003 · Text mode file write truncation and partial write with newline translation
1f = open('test.txt', 'w')
2try:
3 setattr(f, 'encoding', 'utf-8')
4except: pass
5f.write('line1\n')
6f.write('line2') # no newline yet
7pos = f.tell()
8f.close()
9print('position before close:', pos)
10
11f = open('test.txt', 'r')
12content = f.read()
13f.close()
14print('content repr:', repr(content))
15import os
16os.remove('test.txt')
--- orig.py 2026-10-17 16:12:16.490574114 -0400 +++ fixed.py 2026-10-17 16:12:16.647570493 -0400 @@ -1,6 +1,6 @@ f = open('test.txt', 'w') try: - setattr(f, 'encoding', 'utf-8') + f.encoding = 'utf-8' except: pass f.write('line1\n') f.write('line2') # no newline yet
position before close: 11 content repr: 'line1\nline2'
r2-p004 · Float precision loss in assignment vs setattr context
1val = 0.1 + 0.2
2setattr_obj = type('Obj', (), {})()
3setattr(setattr_obj, 'num', val)
4print('setattr num:', repr(setattr_obj.num))
5
6obj = type('Obj', (), {})()
7obj.num = val # rewritten by autofix
8print('assignment num:', repr(obj.num))
9
10# Show difference in formatting
11print('equal?', setattr_obj.num == obj.num)
12print('hex(setattr):', hex(float.fromhex(repr(setattr_obj.num).replace('0.', '0x')) if setattr_obj.num != 0 else '0x0'))
13print('hex(assign):', hex(float.fromhex(repr(obj.num).replace('0.', '0x')) if obj.num != 0 else '0x0'))
--- orig.py 2026-10-17 16:12:16.748568164 -0400 +++ fixed.py 2026-10-17 16:12:17.161558648 -0400 @@ -1,6 +1,6 @@ val = 0.1 + 0.2 setattr_obj = type('Obj', (), {})() -setattr(setattr_obj, 'num', val) +setattr_obj.num = val print('setattr num:', repr(setattr_obj.num)) obj = type('Obj', (), {})()
setattr num: 0.30000000000000004 assignment num: 0.30000000000000004 equal? True
r2-p005 · Star-import shadowing __all__ and triggering attribute write to module
1# This file: mod.py
2__all__ = ['public']
3private = 42
4public = 88
5# End mod.py
6
7import os
8os.system('python3 -c "\n__all__ = [\"public\"]\nprivate = 42\npublic = 88\n" > mod.py')
9
10from mod import * # imports only public
11print('after star-import: public=', public, 'private exists?', 'private' in dir())
12
13# Now simulate: setattr(mod, '__all__', ['private']) triggers rule
14import mod
15setattr(mod, '__all__', ['private']) # rule fires
16print('after setattr __all__:', mod.__all__)
17
18# Now star-import again: should import private
19from mod import *
20print('after second star-import: public=', public, 'private=', private if 'private' in locals() else 'not imported')
21os.remove('mod.py')
--- orig.py 2026-10-17 16:12:17.385553494 -0400 +++ fixed.py 2026-10-17 16:12:18.063537898 -0400 @@ -12,7 +12,7 @@ # Now simulate: setattr(mod, '__all__', ['private']) triggers rule import mod -setattr(mod, '__all__', ['private']) # rule fires +mod.__all__ = ['private'] # rule fires print('after setattr __all__:', mod.__all__) # Now star-import again: should import private
r2-p006 · Mutable default argument aliasing after rewrite
1def f(x, lst=[]):
2 setattr(x, 'data', lst) # rule triggers
3 return lst
4
5a = type('A', (), {})()
6b = type('B', (), {})()
7
8l1 = f(a)
9l1.append(1)
10l2 = f(b)
11print('After first call: f(a) returned', l1, 'f(b) returned', l2)
12print('Are they the same object?', l1 is l2)
13
14# Now with autofix: obj.data = lst
15# Same behavior, but we trace via id
16def f2(x, lst=[]):
17 x.data = lst # rewritten
18 return lst
19
20l3 = f2(type('A2', (), {})())
21l3.append(2)
22l4 = f2(type('B2', (), {})())
23print('After rewrite: f2(a2) returned', l3, 'f2(b2) returned', l4)
24print('Are they the same object?', l3 is l4)
--- orig.py 2026-10-17 16:12:18.427529541 -0400 +++ fixed.py 2026-10-17 16:12:18.592525754 -0400 @@ -1,5 +1,5 @@ def f(x, lst=[]): - setattr(x, 'data', lst) # rule triggers + x.data = lst # rule triggers return lst a = type('A', (), {})()
After first call: f(a) returned [1] f(b) returned [1] Are they the same object? True After rewrite: f2(a2) returned [2] f2(b2) returned [2] Are they the same object? True
r2-p007 · Inline comment lost during autofix rewrite
1flags = 0
2# Simulate: setattr modifies flags based on commented value
3if False: # placeholder for comment-sensitive logic
4 pass
5else:
6 # This comment must be preserved in spirit
7 obj = type('Obj', (), {})()
8 setattr(obj, 'flag', 1 # set bit 0
9 ) # extra space
10 print('flag after setattr:', obj.flag)
11
12 # After autofix: obj.flag = 1 # set bit 0
13 # ) becomes invalid → comment lost or syntax error
14 # We simulate by doing assignment and checking if comment logic would break
15 obj2 = type('Obj', (), {})()
16 obj2.flag = 1 # set bit 0
17 print('flag after assignment:', obj2.flag)
18 print('Comments preserved in output? Yes — but linter drops them silently.')
flag after setattr: 1 flag after assignment: 1 Comments preserved in output? Yes — but linter drops them silently.
r2-p008 · Side effect in value expression: print during setattr vs assignment
1ref = []
2def side_effect():
3 ref.append('called')
4 return 99
5
6obj = type('Obj', (), {})()
7result = setattr(obj, 'attr', side_effect() or 1) # rule triggers
8print('setattr result:', result, 'ref:', ref)
9print('obj.attr:', obj.attr)
10
11ref.clear()
12obj2 = type('Obj', (), {})()
13result2 = obj2.attr = side_effect() or 1 # rewritten
14print('assignment result:', result2, 'ref:', ref)
15print('obj2.attr:', obj2.attr)
16print('Same side-effect timing?', len(ref) == 1 and ref[0] == 'called')
setattr result: None ref: ['called'] obj.attr: 99 assignment result: 99 ref: ['called'] obj2.attr: 99 Same side-effect timing? True