ruff FURB103
labelled unknown
Analysis Nemotron 3 Super
The rule flags manual file opening and writing using 'with open(...)' followed by '.write()', suggesting replacement with pathlib.Path.write_text/write_bytes.
Fix: Replace 'with open(path, mode) as f: f.write(content)' with 'Path(path).write_text(content)' for text modes or 'Path(path).write_bytes(content)' for binary modes, assuming no additional file operations or comments within the block.
10 hazards
- The rewrite drops any comments inside the 'with' block, altering source code documentation.
- If the file mode includes 'a' (append), the fix incorrectly overwrites instead of appending.
- Using buffering=1 or other non-default flags in open() changes I/O behavior; the fix ignores these flags.
- When multiple write() calls exist in the block, the fix only replaces the first, leaving others unchanged and causing syntax errors.
- If the write() argument is not a simple string/bytes but an expression with side effects, evaluating it once in the fix may differ from multiple evaluations in the original.
- The original 'open' may create the file descriptor used elsewhere via aliasing (e.g., passing 'f' to another function); the fix breaks such aliases.
- If an exception occurs during open(), the with statement ensures cleanup; the fix changes error handling timing and scope.
- The fix assumes the path is a string or Path object, but if it's an expression (e.g., f'{dir}/file.txt'), the rewrite may not preserve evaluation order or side effects.
- In binary mode, if the written data is not bytes (e.g., a string encoded implicitly), the fix may raise TypeError where the original succeeded via str.encode().
- The fix introduces a top-level Path() call that may fail if the module lacks 'from pathlib import Path', requiring an import that wasn't needed before.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Expression path evaluation order | The fix assumes the path is a simple variable or literal, but if it's an expression like f'{dir}/file.txt', rewriting to Path(path).write_text(...) may change evaluation order or cause repeated evalua |
| r1-p002 | fix ok | Walrus in comprehension scope | Walrus, comprehensions scoping, late binding in closures and lambdas |
| r1-p003 | fix ok | Append mode incorrectly overwritten | If the file mode includes 'a' (append), the fix incorrectly overwrites instead of appending. |
| r1-p004 | silent prediction off | File descriptor aliasing broken | The original 'open' may create the file descriptor used elsewhere via aliasing (e.g., passing 'f' to another function); the fix breaks such aliases. |
Sources
r1-p001 · Expression path evaluation order
1import os
2counter = 0
3def get_path():
4 global counter
5 counter += 1
6 return f'test{counter}.txt'
7with open(get_path(), 'w') as f:
8 f.write('hello')
9with open(get_path(), 'r') as f:
10 print(f.read())
11if os.path.exists('test1.txt'):
12 os.remove('test1.txt')
13if os.path.exists('test2.txt'):
14 os.remove('test2.txt')
15print(f'counter={counter}')
--- orig.py 2026-10-22 14:31:15.092109627 -0400 +++ fixed.py 2026-10-22 14:31:18.269123527 -0400 @@ -1,11 +1,11 @@ import os +import pathlib counter = 0 def get_path(): global counter counter += 1 return f'test{counter}.txt' -with open(get_path(), 'w') as f: - f.write('hello') +pathlib.Path(get_path()).write_text('hello') with open(get_path(), 'r') as f: print(f.read()) if os.path.exists('test1.txt'):
r1-p002 · Walrus in comprehension scope
1results = []
2for i in range(2):
3 with open(f'file{i}.txt', 'w') as f:
4 f.write(str(i))
5 results.append(lambda: open(f'file{i}.txt').read())
6print([r() for r in results])
7for i in range(2):
8 if os.path.exists(f'file{i}.txt'):
9 os.remove(f'file{i}.txt')
--- orig.py 2026-10-22 14:31:15.093109631 -0400 +++ fixed.py 2026-10-22 14:31:18.859126179 -0400 @@ -1,7 +1,7 @@ +import pathlib results = [] for i in range(2): - with open(f'file{i}.txt', 'w') as f: - f.write(str(i)) + pathlib.Path(f'file{i}.txt').write_text(str(i)) results.append(lambda: open(f'file{i}.txt').read()) print([r() for r in results]) for i in range(2):
['1', '1']
r1-p003 · Append mode incorrectly overwritten
1with open('log.txt', 'w') as f:
2 f.write('first\n')
3with open('log.txt', 'a') as f:
4 f.write('second\n')
5with open('log.txt', 'r') as f:
6 print(f.read(), end='')
7if os.path.exists('log.txt'):
8 os.remove('log.txt')
--- orig.py 2026-10-22 14:31:15.094109636 -0400 +++ fixed.py 2026-10-22 14:31:18.900126363 -0400 @@ -1,5 +1,5 @@ -with open('log.txt', 'w') as f: - f.write('first\n') +import pathlib +pathlib.Path('log.txt').write_text('first\n') with open('log.txt', 'a') as f: f.write('second\n') with open('log.txt', 'r') as f:
first second
r1-p004 · File descriptor aliasing broken
1def write_and_return(f):
2 f.write('test')
3 return f
4with open('tmp.txt', 'w') as f:
5 f2 = write_and_return(f)
6 print(f2.write(' more'))
7with open('tmp.txt', 'r') as f:
8 print(f.read())
9if os.path.exists('tmp.txt'):
10 os.remove('tmp.txt')
5 test more