Compose, format, interpolate, or inspect text.
An f-string is a fill-in-the-blanks form whose blanks are evaluated now and frozen into a real string.
template: f'Hello {name}, {bal:.2f}'
|
| eval each {expr} at runtime
v
result: 'Hello Ada, 12.50'
Strings are immutable sequences of Unicode code points. f-strings (PEP 498) compile and evaluate any {expr} at runtime - the fastest of the formatting family. Format specs: .2f, >10, ^, !r. The 3.8+ = self-doc form f'{x=}' shows name=value. f-strings are NOT a safener; never interpolate user input into SQL or HTML.
name = 'Ada'
print(f'Hello {name}!')
print(f'{name=}')
positional interpolation + the = self-doc form for quick debugging.
pi = 3.14159
print(f'{pi:.2f}') # '3.14'
print(f'{pi:>10.4f}') # right-aligned
format specs control width, alignment, fill, and precision.
# opposite quote wrapper to avoid escaping
print(f"{data['name']} is {data['age']}")
wrap f-string in opposite quotes to keep inner quotes unescaped.
Treating f-strings as SQL/HTML safeners. f-strings do NOT escape - interpolating user input via f-string into a SQL query is the canonical SQLi vector. Use parameterised queries; use Jinja2 autoescape for HTML.
f-strings: fast, in-source, evaluated now. Use for logs/UI. NEVER for SQL/HTML/JS with user input. Reach for = debug form when poking.
<script>alert(1)</script> runs for admins viewing the profile.html = f'<p>Hello {user.name}</p>' unescaped; Jinja2 disabled.markupsafe.escape.default-src 'self'.' OR 1=1 -- got auth; DROP TABLE wiped users.q = f"... WHERE name='{name}'" then .execute(q) without binding.cur.execute('... name=%s', (name,)).