Skip to main content
🪢
PEP 701 · Speedrun · Python 3.12 (2023)

PEP 701 in 90 seconds — f-strings finally accept any quotes inside

The cursed quote-switching workaround can finally go.

PEP 498 gave us f-strings in 2016. But until Python 3.12, this was a `SyntaxError`: ```python items = ["a", "b", "c"] f"items: {", ".join(items)}" # ← SyntaxError pre-3.12 ``` Why? The parser tokenised f-strings as a single string token. The inner `"` closed the f-string. For 7 years the workaround was "alternate quote styles per nesting level" — fine for two levels, hellish for three.

PEP 701 (Python 3.12) reimplemented the f-string parser to handle **any Python expression** inside `{}`: - same quote style as the outer f-string ✅ - backslashes inside the expression ✅ - multi-line expressions and `# comments` ✅ - arbitrary nesting depth ✅ Nobody asked for this in slack. Everyone smiles when their cursed quote-switching workaround finally goes.

Before — Python ≤ 3.11, alternate quotes or capture-first
items = ["a", "b", "c"]

# Works (mismatched outer/inner quotes):
print(f"items: {', '.join(items)}")

# SyntaxError pre-3.12 — inner " closes the f-string:
# print(f"items: {", ".join(items)}")
#                  ^

# Workaround: capture separator in a variable first
sep = ", "
print(f"items: {sep.join(items)}")
After — Python 3.12+, full nesting
items = ["a", "b", "c"]
data = {"a": 1, "b": 2, "c": 3}

# Same quotes inside — fine
print(f"items: {", ".join(items)}")

# Multi-line expression with a comment — fine
print(f"total: {
    sum(data.values())  # add up all values
}")

# Backslash in the expression — finally legal
print(f"path: {"\n".join(['/etc', '/usr', '/var'])}")

🎯 Predict the output

What does this print? (PEP 701 is a parser change — runtime semantics of f-strings are unchanged.)

data = {"a": 1, "b": 2, "c": 3}
result = f"keys: {", ".join(data.keys())} | total: {sum(data.values())}"
print(result)
Modern Python idioms → Foundations track

Or speedrun another PEP

PEP 572Assignment as an expression — 3 lines become 1.
PEP 572 in 90 seconds — the walrus that ate your for-loop
PEP 484Python stays dynamic — but you can opt into typing.
PEP 484 in 90 seconds — type hints, without TypeScript-envy
PEP 622Switch statements are a 5% feature. Pattern matching is the 95%.
PEP 622 in 90 seconds — match / case isn't just a switch