ruff FURB161
labelled unknown
Analysis Nemotron 3 Super
Flags calls to bin(x).count('1') where x is an integer expression, recommending replacement with x.bit_count().
Fix: Replaces bin(X).count('1') with X.bit_count(), preserving the inner expression X exactly as it appeared.
10 hazards
- If X is a function call with side effects, the rewrite changes evaluation timing but not frequency—still one call—but may affect interleaving with other expressions in larger statements.
- If X raises an exception (e.g., AttributeError, TypeError), the exception type and timing remain identical; however, if X returns a non-int that defines __index__ but not bit_count (e.g., custom numeric type), the rewrite will raise AttributeError where the original would succeed after bin conversion.
- If X returns a float, both versions raise TypeError, but the original does so during bin() conversion while the rewrite raises AttributeError on bit_count lookup, altering the exception type.
- If X returns a bool, both work (bool inherits bit_count), but the rewrite may be misleading stylistically though behaviorally equivalent.
- If X is an expression whose value is used elsewhere in the same statement (e.g., in a tuple or assignment), the rewrite preserves the value and any side effects, but intermediate reuse of X is not possible since it's evaluated once in both forms.
- If X is a conditional expression like a if cond else b, the rewrite preserves short-circuiting behavior identically.
- If the code contains a comment between bin( and .count (e.g., bin(/* hint */x).count('1')), the autofix will drop the comment as it only preserves the inner expression.
- If X is a lambda or comprehension that captures local state, the rewrite preserves capture semantics since evaluation is otherwise identical.
- If X is a super() call or attribute access on a nested object (e.g., bin(self.obj.x).count('1')), the rewrite preserves the same attribute lookup chain.
- If the code relies on bin(X) returning a string for further use (impossible here since .count('1') is chained), no issue arises—the intermediate string is never accessible.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | Float argument changes exception type from TypeError to AttributeError | If X returns a float, both versions raise TypeError, but the original does so during bin() conversion while the rewrite raises AttributeError on bit_count lookup, altering the exception type. |
| r1-p002 | fired, no fix | Conditional expression preserves short-circuiting identically | If X is a conditional expression like a if cond else b, the rewrite preserves short-circuiting behavior identically. |
| r1-p003 | fired, no fix prediction off | Walrus operator scoping preserved in comprehension | walrus, comprehensions scoping, late binding in closures and lambdas |
| r1-p004 | fired, no fix prediction off | Attribute chain preserved on super() and nested object access | If X is a super() call or attribute access on a nested object (e.g., bin(self.obj.x).count('1')), the rewrite preserves the same attribute lookup chain. |
| r1-p005 | fired, no fix prediction off | Generator vs list: single consumption preserved | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r1-p006 | fix ok prediction off | Context manager with write but no close: no interference | context managers that are not files, objects with write() but no close() |
| r1-p007 | fired, no fix prediction off | Star-imports and module-level side effects: no disruption | star-imports, module-level side effects, __all__ |
| r1-p008 | fired, no fix prediction off | Exception inside rewritten code: finally/else and chaining preserved | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r2-p001 | fired, no fix | Custom Truthiness and Identity | truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' |
| r2-p002 | invalid | Boolean Return Value | If X returns a bool, both work (bool inherits bit_count), but the rewrite may be misleading stylistically though behaviorally equivalent. |
| r2-p003 | fired, no fix | Function Call Side Effects Evaluation Timing | If X is a function call with side effects, the rewrite changes evaluation timing but not frequency—still one call—but may affect interleaving with other expressions in larger statements. |
| r2-p004 | fired, no fix | Evaluation Order and Side Effects in Larger Expressions | evaluation order and side effects when expressions are merged, duplicated or deleted |
| r2-p005 | fired, no fix | Conditional Expression Short-Circuiting | If X is a conditional expression like a if cond else b, the rewrite preserves short-circuiting behavior identically. |
| r2-p006 | invalid | Walrus and Comprehension Scoping | walrus, comprehensions scoping, late binding in closures and lambdas |
| r2-p007 | fired, no fix prediction off | Runtime Type Annotations and Typing-Only Imports | type annotations evaluated at runtime, typing-only imports |
| r2-p008 | invalid | Pathlib vs os.path Behavior | pathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths |
Sources
r1-p001 · Float argument changes exception type from TypeError to AttributeError
1def f():
2 return 3.14
3try:
4 result = bin(f()).count('1')
5except Exception as e:
6 print(type(e).__name__, str(e))
7try:
8 result = f().bit_count()
9except Exception as e:
10 print(type(e).__name__, str(e))
TypeError 'float' object cannot be interpreted as an integer AttributeError 'float' object has no attribute 'bit_count'
r1-p002 · Conditional expression preserves short-circuiting identically
1def g():
2 print('g called')
3 return 5
4def h():
5 print('h called')
6 return 3
7cond = False
8try:
9 v = bin(g() if cond else h()).count('1')
10except: pass
11print('g' if not cond else 'h', 'not called' if cond else 'called')
12cond = True
13try:
14 v = bin(g() if cond else h()).count('1')
15except: pass
16print('g' if cond else 'h', 'called' if cond else 'not called')
h called g called g called g called
r1-p003 · Walrus operator scoping preserved in comprehension
1vals = []
2for i in range(2):
3 vals.append((lambda: [x for j in range(1) if (x := i + j) and bin(x).count('1')])())
4print(vals)
5# After fix: same behavior, walrus scoped correctly
[[], [1]]
r1-p004 · Attribute chain preserved on super() and nested object access
1class A:
2 x = 5
3class B(A):
4 def get(self):
5 return super().x
6obj = B()
7print(bin(obj.get()).count('1'))
8obj2 = type('Obj', (), {'x': 10})()
9print(bin(obj2.x).count('1'))
2 2
r1-p005 · Generator vs list: single consumption preserved
1def gen():
2 yield 7
3 yield 3
4print(sum(bin(x).count('1') for x in gen()))
5g = gen()
6print(list(g)) # should be empty after first consumption
5 [7, 3]
r1-p006 · Context manager with write but no close: no interference
1class Writable:
2 def __enter__(self):
3 self.data = []
4 return self
5 def write(self, x):
6 self.data.append(x)
7 def __exit__(self, *args):
8 pass
9with Writable() as w:
10 w.write(str(bin(15).count('1')))
11print(w.data)
--- orig.py 2026-10-17 17:34:12.949814493 -0400 +++ fixed.py 2026-10-17 17:34:13.111812519 -0400 @@ -7,5 +7,5 @@ def __exit__(self, *args): pass with Writable() as w: - w.write(str(bin(15).count('1'))) + w.write(str((15).bit_count())) print(w.data)
['4']
r1-p007 · Star-imports and module-level side effects: no disruption
1import sys
2mod = type('M', (), {})
3mod.VALUE = 42
4sys.modules['mymod'] = mod
5from mymod import *
6print(VALUE)
7print(bin(VALUE).count('1'))
42 3
r1-p008 · Exception inside rewritten code: finally/else and chaining preserved
1def bad():
2 raise ValueError('bad')
3try:
4 try:
5 bin(bad()).count('1')
6 except ValueError as e:
7 raise RuntimeError('worse') from e
8except RuntimeError as e:
9 print(type(e).__name__, str(e))
10 print(type(e.__cause__).__name__, str(e.__cause__) if e.__cause__ else None)
RuntimeError worse ValueError bad
r2-p001 · Custom Truthiness and Identity
1class C:
2 def __init__(self, v): self.v = v
3 def __bool__(self): return self.v > 0
4 def __len__(self): return abs(self.v)
5a = C(-1)
6b = C(0)
7c = C(1)
8print(f'a_bool={bool(a)} a_len={len(a)} a_is_none={a is None}')
9print(f'b_bool={bool(b)} b_len={len(b)} b_is_none={b is None}')
10print(f'c_bool={bool(c)} c_len={len(c)} c_is_none={c is None}')
11if bin(a).count('1'): print('a count True')
12else: print('a count False')
13if bin(b).count('1'): print('b count True')
14else: print('b count False')
15if bin(c).count('1'): print('c count True')
16else: print('c count False')
a_bool=False a_len=1 a_is_none=False b_bool=False b_len=0 b_is_none=False c_bool=True c_len=1 c_is_none=False
r2-p002 · Boolean Return Value (generated program does not parse: Traceback (most recent call last):)
1def f(): return True
2def g(): return False
3print(f'f()={f()} g()={g()}')
4print(f'bin(f()).count(\'1\')={bin(f()).count(\'1\')} f().bit_count()={f().bit_count()}')
5print(f'bin(g()).count(\'1\')={bin(g()).count(\'1\')} g().bit_count()={g().bit_count()}')
6x = f()
7y = g()
8if bin(x).count('1') == x.bit_count(): print('Match for f()')
9else: print('Mismatch for f()')
10if bin(y).count('1') == y.bit_count(): print('Match for g()')
11else: print('Mismatch for g()')
r2-p003 · Function Call Side Effects Evaluation Timing
1state = []
2def log(n):
3 state.append(f'called {n}')
4 return n * 2
5print('Start:', state)
6try:
7 val = bin(log(3)).count('1')
8except Exception as e:
9 print(f'Error: {type(e).__name__}: {e}')
10print('After bin(log(3)).count(\'1\'):', state)
11state.clear()
12state.append('reset')
13try:
14 val = bin(log(5)).bit_count()
15except Exception as e:
16 print(f'Error: {type(e).__name__}: {e}')
17print('After bin(log(5)).bit_count():', state)
Start: []
After bin(log(3)).count('1'): ['called 3']
Error: AttributeError: 'str' object has no attribute 'bit_count'
After bin(log(5)).bit_count(): ['reset', 'called 5']r2-p004 · Evaluation Order and Side Effects in Larger Expressions
1side = []
2def f():
3 side.append('f')
4 return 3
5def g():
6 side.append('g')
7 return 5
8side.clear()
9result = f() + bin(g()).count('1')
10print(f'f then g: side={side}, result={result}')
11side.clear()
12result = bin(f()).count('1') + g()
13print(f'bin(f) then g: side={side}, result={result}')
14side.clear()
15result = f() + bin(g()).bit_count()
16print(f'f then bit_count g: side={side}, result={result}')
17side.clear()
18result = bin(f()).bit_count() + g()
19print(f'bit_count f then g: side={side}, result={result}')
f then g: side=['f', 'g'], result=5 bin(f) then g: side=['f', 'g'], result=7
r2-p005 · Conditional Expression Short-Circuiting
1def a(): print('a called'); return 1
2def b(): print('b called'); return 2
3cond = False
4print('Testing bin(a() if cond else b()).count(\'1\'):')
5try:
6 r = bin(a() if cond else b()).count('1')
7except Exception as e:
8 print(f' Error: {type(e).__name__}: {e}')
9cond = True
10print('Testing bin(a() if cond else b()).count(\'1\'):')
11try:
12 r = bin(a() if cond else b()).count('1')
13except Exception as e:
14 print(f' Error: {type(e).__name__}: {e}')
15cond = False
16print('Testing bin(a() if cond else b()).bit_count():')
17try:
18 r = bin(a() if cond else b()).bit_count()
19except Exception as e:
20 print(f' Error: {type(e).__name__}: {e}')
21cond = True
22print('Testing bin(a() if cond else b()).bit_count():')
23try:
24 r = bin(a() if cond else b()).bit_count()
25except Exception as e:
26 print(f' Error: {type(e).__name__}: {e}')
Testing bin(a() if cond else b()).count('1'):
b called
Testing bin(a() if cond else b()).count('1'):
a called
Testing bin(a() if cond else b()).bit_count():
b called
Error: AttributeError: 'str' object has no attribute 'bit_count'
Testing bin(a() if cond else b()).bit_count():
a called
Error: AttributeError: 'str' object has no attribute 'bit_count'r2-p006 · Walrus and Comprehension Scoping (generated program does not parse: Traceback (most recent call last):)
1lst = [5, 6, 7]
2print('List comprehension with walrus:')
3squares = [ (n := x) * n for x in lst if bin(n := x).count('1') == 2 ]
4print(f' Result: {squares}, final n: {n}')
5print('
6Generator expression capture:')
7gen = (bin(n := x).count('1') for x in lst)
8first = next(gen)
9print(f' First from gen: {first}, n after: {n}')
10second = next(gen)
11print(f' Second from gen: {second}, n after: {n}')
12print('
13Lambda late-binding:')
14funcs = [lambda x=x: bin(n := x).count('1') for x in lst]
15for f in funcs:
16 print(f' Lambda result: {f()}')
r2-p007 · Runtime Type Annotations and Typing-Only Imports
1from typing import List
2def process(x: int) -> int:
3 return bin(x).count('1')
4print(f'process(5) = {process(5)}')
5print(f'Annotations: {process.__annotations__}')
6try:
7 if bin(process.__annotations__['return']).count('1'):
8 print('Return annotation has odd popcount')
9 else:
10 print('Return annotation has even popcount')
11except Exception as e:
12 print(f'Error checking annotation: {type(e).__name__}: {e}')
process(5) = 2
Annotations: {'x': <class 'int'>, 'return': <class 'int'>}
Error checking annotation: TypeError: 'type' object cannot be interpreted as an integerr2-p008 · Pathlib vs os.path Behavior (generated program does not parse: Traceback (most recent call last):)
1import os
2open('test.txt', 'w').write('hello')
3try:
4 p_len = len('test.txt')
5 print(f'Path length: {p_len}')
6 print(f'bin(len(\'test.txt\')).count(\'1\') = {bin(p_len).count(\'1\')}')
7 print(f'bin(len(\'test.txt\')).bit_count() = {p_len.bit_count()}')
8 os.symlink('test.txt', 'link.txt')
9 l_len = len(os.readlink('link.txt'))
10 print(f'Symlink target length: {l_len}')
11 print(f'bin(len(os.readlink(\'link.txt\'))).count(\'1\') = {bin(l_len).count(\'1\')}')
12 print(f'bin(len(os.readlink(\'link.txt\'))).bit_count() = {l_len.bit_count()}')
13except Exception as e:
14 print(f'Error: {type(e).__name__}: {e}')
15finally:
16 try:
17 os.unlink('test.txt')
18 os.unlink('link.txt')
19 except FileNotFoundError:
20 pass