Strings & f-strings
Python str objects represent Unicode text. f-strings embed expressions directly in literals, making formatting readable and fast. Knowing when to use bytes prevents encoding bugs at system boundaries.
Recipe
name = "Ada"
score = 97.456
line = f"{name:>10} | {score:.1f}%"
multiline = f"""Report for {name}
Score: {score:.2f}"""
raw_path = r"C:\new\data\file.txt"When to reach for this:
- Building log messages and user-facing output
- Parsing delimited text (CSV-like) with split/strip
- Encoding text for network or file I/O
- Normalizing user input before validation
Working Example
from pathlib import Path
def build_report(rows: list[dict[str, object]]) -> str:
header = f"{'name':<12} {'role':<10} {'score':>6}"
lines = [header, "-" * len(header)]
for row in rows:
lines.append(
f"{row['name']:<12} {row['role']:<10} {row['score']:>6.1f}"
)
return "\n".join(lines)
def slugify(text: str) -> str:
cleaned = text.strip().lower()
for ch in " /_":
cleaned = cleaned.replace(ch, "-")
while "--" in cleaned:
cleaned = cleaned.replace("--", "-")
return cleaned.strip("-")
def read_utf8_file(path: Path) -> str:
return path.read_text(encoding="utf-8")
def to_bytes_payload(message: str) -> bytes:
return message.encode("utf-8")
if __name__ == "__main__":
sample = [
{"name": "Ada", "role": "admin", "score": 98.2},
{"name": "Linus", "role": "dev", "score": 91.0},
]
print(build_report(sample))
print(slugify(" Hello World / API "))
print(to_bytes_payload("ok"))What this demonstrates:
- f-string format specs align columns (
:<12,:>6.1f) strip,lower, andreplaceform a simple slug pipelinePath.read_text(encoding="utf-8")avoids platform-default encoding surprisesstr.encodeproducesbytesfor wire protocols
Deep Dive
How It Works
- Immutable sequences - Strings cannot change in place; methods return new strings.
- Unicode code points - Python 3
stris Unicode; indexing returns one-code-point strings (mostly). - f-string evaluation - Expressions run at runtime; debug
f"{x=}"shows name and value (3.8+). - bytes vs str -
bytesholds 0-255 values; decode with an explicit codec. - Normalization -
unicodedata.normalizehelps compare visually similar characters.
Common Methods
| Method | Purpose |
|---|---|
split(sep) | Break on delimiter |
join(iterable) | Concatenate with separator |
strip(chars) | Trim ends |
replace(old, new) | Substitute substrings |
removeprefix/prefix | Trim known prefix (3.9+) |
Python Notes
text = " hello "
text.strip().casefold() == "HELLO".casefold() # case-insensitive compare
b"\xc3\xa9".decode("utf-8") # bytes -> str
"résumé".encode("ascii", errors="replace") # lossy fallbackGotchas
- Implicit encoding -
open("f.txt")uses locale default on some systems. Fix: Always passencoding="utf-8". - Concatenating bytes and str -
b"hi" + "there"raises TypeError. Fix: Encode or decode explicitly first. - Mutable default string building - Huge
+=in loops is slow for massive text. Fix: Collect in a list and"".join(lines). - f-string quoting - Nested quotes can break parsing. Fix: Use different quote styles or variables.
- Comparing Unicode visually -
"café" != "café"with different combining characters. Fix: Normalize before compare.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| f-strings | Most formatting | Need lazy template for i18n catalogs |
str.format | Positional templates from config | Simple inline formatting |
Template | Safer substitution from user input | Full format spec needs |
regex (re) | Complex pattern extraction | Simple split suffices |
FAQs
Are f-strings faster than format()?
Generally yes - expressions are evaluated once at runtime with less overhead. Prefer f-strings for new code.
When do I need bytes instead of str?
Network protocols, binary file formats, and crypto digests operate on bytes. Convert at the boundary.
What encoding should I default to?
UTF-8 for text files, HTTP JSON, and logs unless a spec mandates otherwise.
How do I do an f-string multiline?
Use triple quotes: f"""line1\n{name}""". Newlines in the literal are preserved.
What is a raw string?
r"\\server\share" treats backslashes literally - handy for regex and Windows paths in docs.
How do I format thousands separators?
f"{1_000_000:,}" produces 1,000,000. Underscores in literals are cosmetic for readability too.
Can f-strings call functions?
Yes: f"{len(items)} items". Keep expressions simple for readability.
How do I strip punctuation?
str.strip removes only ends. For all occurrences use replace or re.sub.
What does casefold do?
Aggressive case normalization for comparisons - better than lower() for Unicode matching.
How do I safely build SQL strings?
Never interpolate user input. Use parameterized queries from your DB driver or ORM.
Related
- Files & I/O - reading and writing encoded text
- Numbers, Booleans & Arithmetic - numeric format specs
- Comprehensions & Generator Expressions - string joins from iterables
- arrays, bytes & memoryview - binary buffers
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.