ruff F541
labelled unknown
Analysis Nemotron 3 Super
Flags f-strings that contain no placeholder expressions.
Fix: Rewrites the f-string literal by removing the leading 'f' (or 'rf'/'fr') prefix, converting it to a regular string literal (or raw string if applicable).
10 hazards
- If the f-string is part of a docstring (e.g., immediately follows a function/class definition), removing the 'f' may preserve the docstring but Ruff marks the fix unsafe in such cases—however, if incorrectly applied, it could alter tooling that distinguishes docstrings by presence of 'f'.
- In a line-continued context using backslash (e.g., `f"hello" \ f"world"`), removing the 'f' from one segment changes the type of that segment from f-string to str, potentially breaking concatenation semantics if the other segment relies on interpolation (though rule only fires when no segment has placeholders).
- If the f-string contains escaped quote characters (e.g., `f"He said \"hi\""`), the fix preserves them correctly since they are literal in both f-string and regular string contexts.
- When the f-string uses triple quotes (e.g., `f"""hello"""`), the fix produces a triple-quoted regular string, which behaves identically for content without placeholders or escape dependencies.
- If the f-string is a raw f-string (e.g., `rf"\n"`), the fix removes the 'f' to yield `r"\n"`, preserving raw string behavior; however, if the original relied on f-string-specific raw processing (which doesn't differ from raw string for non-interpolated content), it is safe—but Ruff may still consider it hazardous due to prefix alteration.
- If the f-string appears inside a larger expression where its type is used in overload resolution or isinstance checks (rare), changing it from f-string to str could theoretically affect behavior, though both are instances of str and share the same runtime type.
- If the f-string is adjacent to another string literal without an operator (implicit concatenation) and only the f-string is modified (e.g., `f"hello" "world"` → `"hello" "world"`), the concatenation result remains unchanged as both are string literals.
- If the f-string contains a comment inside (invalid syntax—comments cannot be inside string literals), this is not a concern; however, if the line has a trailing comment (e.g., `f"hi" # note`), the fix preserves the comment as it lies outside the literal.
- If the f-string is generated via a macro or code‑generation tool that expects the 'f' prefix for later processing, removing it could break downstream tooling, though this is outside Python semantics.
- If the f-string is part of a stub file (.pyi) and the fixer runs there, altering the literal may affect type checker expectations, though f-strings without placeholders are uncommon in stubs and behave identically as str.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Walrus operator in loop with f-string condition | walrus, comprehensions scoping, late binding in closures and lambdas |
| r1-p002 | fix ok | F-string with percent formatting inside | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
| r1-p003 | fix ok | F-string encoded to bytes and written to file | bytes vs str, encoding and newline handling, text vs binary modes |
| r1-p004 | fix ok | File write with f-string and truncation on reuse | file modes and truncation, partial writes, non-str data passed to write |
| r1-p005 | fix ok prediction off | Escaped quotes in f-string preserved after fix | If the f-string contains escaped quote characters (e.g., `f"He said \"hi\"")`, the fix preserves them correctly since they are literal in both f-string and regular string contexts. |
| r1-p006 | fix ok | Macro-generated f-string expecting prefix for processing | If the f-string is generated via a macro or code‑generation tool that expects the 'f' prefix for later processing, removing it could break downstream tooling, though this is outside Python semantics. |
| r1-p007 | fix ok | Star-import and module-level side effect with f-string | star-imports, module-level side effects, __all__ |
| r1-p008 | invalid | Line-continued f-string concatenation with mixed segments | In a line-continued context using backslash (e.g., `f"hello" \ f"world"`), removing the 'f' from one segment changes the type of that segment from f-string to str, potentially breaking concatenati |
| r2-p001 | fix ok | Raw f-string with escape sequence | raw f-string prefix removal alters interpretation of escapes |
| r2-p002 | fix ok | Context manager with write but no close | object with write() method used as file substitute |
| r2-p003 | fix ok | File write with truncation on reuse | file mode and truncation behavior affects output |
| r2-p004 | silent prediction off | Type annotation evaluated at runtime | typing-only import used in annotation that executes at runtime |
| r2-p005 | silent prediction off | Float coercion and rounding | numeric expression in f-string subject to rounding |
| r2-p006 | silent | F-string in docstring position | removing f from docstring-altering string affects tooling |
| r2-p007 | silent prediction off | Evaluation order with side effects | side effects in f-string expression affect execution order |
| r2-p008 | silent prediction off | Walrus in comprehension with f-string condition | walrus operator in comprehension scoping affected by f-string rewrite |
Sources
r1-p001 · Walrus operator in loop with f-string condition
1results = []
2for i in range(3):
3 if (s := f"count") == "count":
4 results.append(i)
5 def make_closure():
6 return s
7 results.append(make_closure())
8print(results)
--- orig.py 2026-10-17 16:53:59.061364670 -0400 +++ fixed.py 2026-10-17 16:53:59.227367318 -0400 @@ -1,6 +1,6 @@ results = [] for i in range(3): - if (s := f"count") == "count": + if (s := "count") == "count": results.append(i) def make_closure(): return s
[0, 'count', 1, 'count', 2, 'count']
r1-p002 · F-string with percent formatting inside
1template = f"Hello"
2value = template % {}
3print(value)
4try:
5 f"{template}"
6except Exception as e:
7 print(type(e).__name__, str(e))
--- orig.py 2026-10-17 16:53:59.332368993 -0400 +++ fixed.py 2026-10-17 16:53:59.494371576 -0400 @@ -1,4 +1,4 @@ -template = f"Hello" +template = "Hello" value = template % {} print(value) try:
Hello
r1-p003 · F-string encoded to bytes and written to file
1data = f"test"
2with open('tmp.bin', 'wb') as f:
3 f.write(data.encode())
4with open('tmp.bin', 'rb') as f:
5 print(f.read())
6import os
7os.remove('tmp.bin')
--- orig.py 2026-10-17 16:53:59.597373219 -0400 +++ fixed.py 2026-10-17 16:53:59.767375931 -0400 @@ -1,4 +1,4 @@ -data = f"test" +data = "test" with open('tmp.bin', 'wb') as f: f.write(data.encode()) with open('tmp.bin', 'rb') as f:
b'test'
r1-p004 · File write with f-string and truncation on reuse
1with open('tmp.txt', 'w') as f:
2 f.write(f"first")
3with open('tmp.txt', 'a') as f:
4 f.write(f"second")
5with open('tmp.txt', 'r') as f:
6 print(f.read())
7import os
8os.remove('tmp.txt')
--- orig.py 2026-10-17 16:53:59.868377542 -0400 +++ fixed.py 2026-10-17 16:54:00.042380317 -0400 @@ -1,7 +1,7 @@ with open('tmp.txt', 'w') as f: - f.write(f"first") + f.write("first") with open('tmp.txt', 'a') as f: - f.write(f"second") + f.write("second") with open('tmp.txt', 'r') as f: print(f.read()) import os
firstsecond
r1-p005 · Escaped quotes in f-string preserved after fix
1s = f"He said \"hi\""
2print(s.count('\"'))
--- orig.py 2026-10-17 16:54:00.150382038 -0400 +++ fixed.py 2026-10-17 16:54:00.312384620 -0400 @@ -1,2 +1,2 @@ -s = f"He said \"hi\"" +s = "He said \"hi\"" print(s.count('\"'))
2
r1-p006 · Macro-generated f-string expecting prefix for processing
1def process_fstring(src):
2 return src.startswith('f') and 'dynamic' in src
3s = f"dynamic"
4print(process_fstring(s))
--- orig.py 2026-10-17 16:54:00.412386213 -0400 +++ fixed.py 2026-10-17 16:54:00.581388907 -0400 @@ -1,4 +1,4 @@ def process_fstring(src): return src.startswith('f') and 'dynamic' in src -s = f"dynamic" +s = "dynamic" print(process_fstring(s))
False
r1-p007 · Star-import and module-level side effect with f-string
1__all__ = ['VALUE']
2VALUE = f"shared"
3def get_value():
4 return VALUE
5print(get_value())
--- orig.py 2026-10-17 16:54:00.677390437 -0400 +++ fixed.py 2026-10-17 16:54:00.845393114 -0400 @@ -1,5 +1,5 @@ __all__ = ['VALUE'] -VALUE = f"shared" +VALUE = "shared" def get_value(): return VALUE print(get_value())
shared
r1-p008 · Line-continued f-string concatenation with mixed segments (generated program does not parse: Traceback (most recent call last):)
1x = 42
2s = f"value{" +
3 f"{x}"}
4print(s)
r2-p001 · Raw f-string with escape sequence
1s = rf'\n'
2print(repr(s))
--- orig.py 2026-10-17 16:54:06.414481647 -0400 +++ fixed.py 2026-10-17 16:54:06.585484359 -0400 @@ -1,2 +1,2 @@ -s = rf'\n' +s = r'\n' print(repr(s))
'\\n'
r2-p002 · Context manager with write but no close
1class Logger:
2 def __init__(self):
3 self.buf = []
4 def write(self, data):
5 self.buf.append(data)
6log = Logger()
7with log:
8 log.write(f'hello')
9print(''.join(log.buf))
--- orig.py 2026-10-17 16:54:06.684485929 -0400 +++ fixed.py 2026-10-17 16:54:07.084492273 -0400 @@ -5,5 +5,5 @@ self.buf.append(data) log = Logger() with log: - log.write(f'hello') + log.write('hello') print(''.join(log.buf))
r2-p003 · File write with truncation on reuse
1open('tmp.txt', 'w').write(f'first')
2with open('tmp.txt', 'r+') as f:
3 f.write(f'second')
4 f.truncate()
5with open('tmp.txt') as f:
6 print(f.read())
7import os; os.remove('tmp.txt')
--- orig.py 2026-10-17 16:54:07.310495855 -0400 +++ fixed.py 2026-10-17 16:54:07.477498502 -0400 @@ -1,6 +1,6 @@ -open('tmp.txt', 'w').write(f'first') +open('tmp.txt', 'w').write('first') with open('tmp.txt', 'r+') as f: - f.write(f'second') + f.write('second') f.truncate() with open('tmp.txt') as f: print(f.read())
second
r2-p004 · Type annotation evaluated at runtime
1from typing import List
2T = List[int]
3def f() -> T:
4 return [1]
5x = f()
6print(x)
[1]
r2-p005 · Float coercion and rounding
1val = 1.2345
2print(f'{val:.2f}')
1.23
r2-p006 · F-string in docstring position
1def foo():
2 """This is a docstring"""
3 pass
4print(foo.__doc__)
This is a docstring
r2-p007 · Evaluation order with side effects
1i = 0
2ref = []
3def get():
4 ref.append(i)
5 i += 1
6 return str(i)
7print(f'{get()} {get()}')
8print(ref)
r2-p008 · Walrus in comprehension with f-string condition
1result = [(y := x*2) for x in range(3) if f'{y}' != '0']
2print(result)