ruff FURB105
labelled unknown
Analysis Nemotron 3 Super
Flags print() calls with empty string literals as positional arguments or redundant sep=keyword arguments.
Fix: Removes all empty string literal positional arguments and the sep keyword argument when present, preserving other arguments and keyword arguments like end, file, flush.
10 hazards
- The sep argument might be a variable whose removal avoids evaluating it, changing behavior if it has side effects.
- The sep argument might be a variable whose removal hides a TypeError if it is not a string or None.
- An empty string argument might be intended to produce a specific output format (e.g., CSV with empty fields) and its removal alters output.
- Multiple empty strings might be used for alignment or spacing in output, and removing them changes formatting.
- If sep is a keyword argument passed via **kwargs, the autofix cannot safely remove it and may leave invalid syntax.
- Comments attached to the empty string arguments or sep keyword may be lost during rewriting.
- If the print statement is part of a larger expression (e.g., in a list comprehension), removing arguments could alter syntax validity.
- The sep argument might be conditionally assigned and its removal could skip error checking in debugging contexts.
- In code relying on exact argument count for introspection or decorators, removing args may break assumptions.
- If sep is a built-in overridden locally, removing it may avoid using the local override and use the built-in instead.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Argument Count Introspection | In code relying on exact argument count for introspection or decorators, removing args may break assumptions. |
| r1-p002 | fix ok | Bytes vs Str Encoding | bytes vs str, encoding and newline handling, text vs binary modes |
| r1-p003 | fix ok | Walrus and Comprehension Scoping | walrus, comprehensions scoping, late binding in closures and lambdas |
| r1-p004 | silent | Sep via **kwargs | If sep is a keyword argument passed via **kwargs, the autofix cannot safely remove it and may leave invalid syntax. |
| r1-p005 | fix ok | Print in List Comprehension | If the print statement is part of a larger expression (e.g., in a list comprehension), removing arguments could alter syntax validity. |
| r1-p006 | fix ok | Runtime Type Annotation Evaluation | type annotations evaluated at runtime, typing-only imports |
| r1-p007 | fix ok | Dict Ordering and Hash | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r1-p008 | invalid | Star Imports and Module Side Effects | star-imports, module-level side effects, __all__ |
| r2-p001 | silent prediction off | Dictionary Key Override Behavior | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r2-p002 | silent prediction off | Mutable Default Aliasing | mutable default arguments and aliasing after a rewrite |
| r2-p003 | silent prediction off | Shadowed Built-in Redefinition | shadowed builtins and names redefined later in the module |
| r2-p004 | fix ok | File Truncation and Partial Write | file modes and truncation, partial writes, non-str data passed to write |
| r2-p005 | fix ok | Pathlib Relative Path Symlink | pathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths |
| r2-p006 | invalid | Bytes vs Str Encoding Newline | bytes vs str, encoding and newline handling, text vs binary modes |
| r2-p007 | fix ok | Local Sep Built-in Override | If sep is a built-in overridden locally, removing it may avoid using the local override and use the built-in instead. |
| r2-p008 | fired, no fix | Evaluation Order Side Effects Merged | evaluation order and side effects when expressions are merged, duplicated or deleted |
Sources
r1-p001 · Argument Count Introspection
1def inspect_args(func):
2 import inspect
3 sig = inspect.signature(func)
4 return len(sig.parameters)
5@inspect_args
6def wrapper(*args, **kwargs):
7 print(*args, sep='')
8 return args
9print(wrapper(1, 2))
r1-p002 · Bytes vs Str Encoding
1import io
2buf = io.BytesIO()
3try:
4 print(b''.decode(), file=buf, sep='')
5except Exception as e:
6 print(type(e).__name__, str(e))
7else:
8 print(buf.getvalue())
9buf.close()
--- orig.py 2026-10-17 17:28:16.858393384 -0400 +++ fixed.py 2026-10-17 17:28:17.017390195 -0400 @@ -1,7 +1,7 @@ import io buf = io.BytesIO() try: - print(b''.decode(), file=buf, sep='') + print(b''.decode(), file=buf) except Exception as e: print(type(e).__name__, str(e)) else:
TypeError a bytes-like object is required, not 'str'
r1-p003 · Walrus and Comprehension Scoping
1funcs = []
2for i in range(2):
3 if (x := '') == '':
4 funcs.append(lambda: print(x, sep=''))
5for f in funcs:
6 f()
--- orig.py 2026-10-17 17:28:17.138387771 -0400 +++ fixed.py 2026-10-17 17:28:17.302384486 -0400 @@ -1,6 +1,6 @@ funcs = [] for i in range(2): if (x := '') == '': - funcs.append(lambda: print(x, sep='')) + funcs.append(lambda: print(x)) for f in funcs: f()
r1-p004 · Sep via **kwargs
1kwargs = {'sep': ''}
2print('a', 'b', **kwargs)
3print('done')
ab done
r1-p005 · Print in List Comprehension
1results = [print('', sep='') or 1 for _ in range(2)]
2print('results:', results)
--- orig.py 2026-10-17 17:28:17.528379959 -0400 +++ fixed.py 2026-10-17 17:28:17.689376734 -0400 @@ -1,2 +1,2 @@ -results = [print('', sep='') or 1 for _ in range(2)] +results = [print() or 1 for _ in range(2)] print('results:', results)
results: [1, 1]
r1-p006 · Runtime Type Annotation Evaluation
1T = str
2def foo(x: T) -> None:
3 print(x, '', sep='')
4foo('test')
--- orig.py 2026-10-17 17:28:17.795374611 -0400 +++ fixed.py 2026-10-17 17:28:17.956371386 -0400 @@ -1,4 +1,4 @@ T = str def foo(x: T) -> None: - print(x, '', sep='') + print(x) foo('test')
test
r1-p007 · Dict Ordering and Hash
1class Key:
2 def __init__(self, v): self.v = v
3 def __hash__(self): return hash(self.v)
4 def __eq__(self, other): return self.v == other.v
5d = {Key(''): 1, Key(''): 2}
6print(len(d))
7print('', sep='')
--- orig.py 2026-10-17 17:28:18.060369305 -0400 +++ fixed.py 2026-10-17 17:28:18.226365985 -0400 @@ -4,4 +4,4 @@ def __eq__(self, other): return self.v == other.v d = {Key(''): 1, Key(''): 2} print(len(d)) -print('', sep='') +print()
1
r1-p008 · Star Imports and Module Side Effects (generated program does not parse: Traceback (most recent call last):)
1import tempfile
2import os
3with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
4 f.write('print(__name__, sep=\\'\\'\')
5')
6 fname = f.name
7try:
8 exec(open(fname).read(), {'__name__': '__main__'})
9except Exception as e:
10 print(type(e).__name__, str(e))
11finally:
12 os.unlink(fname)
r2-p001 · Dictionary Key Override Behavior
1class K:
2 def __init__(self, v):
3 self.v = v
4 def __hash__(self):
5 return hash(self.v)
6 def __eq__(self, other):
7 return isinstance(other, K) and self.v == other.v
8k1 = K(1)
9k2 = K(2)
10d = {}
11d[k1] = 'first'
12d[''] = 'empty'
13d[k2] = 'second'
14print(len(d))
15for k, v in d.items():
16 print(f'{k.v if isinstance(k, K) else repr(k)}:{v}', end=' ') if hasattr(k, 'v') else None
3 1:first 2:second
r2-p002 · Mutable Default Aliasing
1def f(a=[], b=''):
2 a.append(1)
3 print(f'a={len(a)}, b={repr(b)}')
4f()
5f()
6f([])
a=1, b='' a=2, b='' a=1, b=''
r2-p003 · Shadowed Built-in Redefinition
1print = lambda *args, **kwargs: None # shadow
2def g():
3 print('', sep='')
4g()
5del print
6print('restored:', end=' ')
7print('hello')
restored: hello
r2-p004 · File Truncation and Partial Write
1with open('test.txt', 'w') as f:
2 f.write('hello')
3 print('', file=f)
4 f.seek(0)
5 f.write('world')
6with open('test.txt') as f:
7 print(repr(f.read()))
8import os
9os.remove('test.txt')
--- orig.py 2026-10-17 17:28:23.093268966 -0400 +++ fixed.py 2026-10-17 17:28:23.265265552 -0400 @@ -1,6 +1,6 @@ with open('test.txt', 'w') as f: f.write('hello') - print('', file=f) + print(file=f) f.seek(0) f.write('world') with open('test.txt') as f:
'world\n'
r2-p005 · Pathlib Relative Path Symlink
1import os
2os.symlink('target', 'link')
3try:
4 with open('link') as f:
5 print('opened link')
6except Exception as e:
7 print(type(e).__name__, e)
8print('', sep='')
9os.unlink('link')
--- orig.py 2026-10-17 17:28:23.370263468 -0400 +++ fixed.py 2026-10-17 17:28:23.531260272 -0400 @@ -5,5 +5,5 @@ print('opened link') except Exception as e: print(type(e).__name__, e) -print('', sep='') +print() os.unlink('link')
FileNotFoundError [Errno 2] No such file or directory: 'link'
r2-p006 · Bytes vs Str Encoding Newline (generated program does not parse: Traceback (most recent call last):)
1data = b'hello
2world'
3print(data.decode())
4with open('out.bin', 'wb') as f:
5 f.write(data)
6with open('out.bin', 'rb') as f:
7 print(f.read())
8import os
9os.remove('out.bin')
r2-p007 · Local Sep Built-in Override
1def test():
2 sep = '|' # local override
3 print('a', '', sep=sep)
4 print('b', '', sep='') # triggers FURB105
5test()
--- orig.py 2026-10-17 17:28:23.793255072 -0400 +++ fixed.py 2026-10-17 17:28:23.954251877 -0400 @@ -1,5 +1,5 @@ def test(): sep = '|' # local override print('a', '', sep=sep) - print('b', '', sep='') # triggers FURB105 + print('b') # triggers FURB105 test()
a| b
r2-p008 · Evaluation Order Side Effects Merged
1i = 0
2def inc():
3 global i
4 i += 1
5 return i
6print('', sep=inc(), end='')
7print(f' i={i}')