2.2Strings, formatting (f-strings) & files
The essentials in 30 seconds
- f-strings everywhere: f"...{var}..." readable, fast, with built-in formatting (:.2f, :.0%) — forget % and .format() outside legacy code.
- split cuts, join assembles, strip cleans: the trio that transforms text — in tests presence.
- with closes by itself: with open(..., encoding="utf-8") + json/csv + pathlib — your programs keep a memory.
Running mini-case — Nabil writes the monthly report
Nabil must send the monthly sales report for Algiers: amounts formatted with two decimals, cleaned names (spaces, case), CSV lines cut and reassembled. His first version concatenates with + and forgets to close his files — broken accents, locked file. He rebuilds: f-strings with :.2f for totals, strip/lower/split/join for cleaning, with open(..., encoding="utf-8") for reading/writing, JSON to make the directory persistent between two runs. In 2.3, these rows will become objects; in 2.4, the missing file will be handled cleanly.
Sequel of the 2.1 running thread: Nabil's sales become a readable, persistent report — and continue in 2.3 and 2.4.
1. f-strings: the only modern way
f"Hello {nom}, total: {total:.2f} €": readable, fast, with built-in formatting (f"{prix:.2f} €", f"{ratio:.0%}"). The variable or computation goes directly inside braces — no manual conversion, no type error. Forget % and .format() outside legacy code: the f-string has been the standard since Python 3.6. Nabil applies it: each line of the monthly report is an f-string with amounts at two decimals and shares computed on the fly.
2. Methods that pay off: split, join, strip
"Algiers, Oran, Blida".split(", ") cuts into a list, ", ".join(liste) assembles (the exact reverse), .strip() cleans input spaces, .lower() and .replace() normalize. .upper() displays, in tests presence (if "@" in email:). These methods do not modify the original string (strings are immutable): they return a new string. Nabil cleans each customer name this way (strip().title()) before comparing it — no more "algiers" vs "Algiers" duplicates.
3. Files: with, encoding, JSON
with open("ventes.txt", encoding="utf-8") as f: — with closes by itself even on error, and encoding="utf-8" avoids broken accents. Modes: "r" reads (default), "w" writes by overwriting, "a" appends at the end. For structure: json.load/dump (persistent lists and dicts — the directory survives a restart), the csv module for spreadsheets, pathlib.Path for clean paths with no concatenation. Nabil saves his summary in JSON with ensure_ascii=False to keep accents readable.
🔎 Concrete example — 🔎 Concrete example — Nabil's persistent JSON directory
The summary survives a restart, as at Nabil's:
import json
from pathlib import Path
fichier = Path("recap.json")
if fichier.exists():
recap = json.loads(fichier.read_text(encoding="utf-8"))
else:
recap = []
recap.append({"wilaya": "Algiers", "total": 1650})
fichier.write_text(json.dumps(recap, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"Wilayas tracked: {len(recap)}")
Reading: pathlib checks existence, json loads and saves, the f-string displays.
Lesson: pathlib replaces path concatenations.
Section vocabulary
- String methods
- Functions attached to text: .upper(), .lower(), .strip() (spaces), .split() (cut), .join() (assemble), .replace(). They return a new string.
Simple example: Nabil cleans a name with nom.strip().title().
What it is not: functions that modify the string in place.
Reflex: transforming text = returned method.
- f-string
- String prefixed with f where {variable} is replaced by its value: f"Hello {nom}". Built-in formatting (:.2f, :.0%). Recommended method since Python 3.6.
Simple example: Nabil writes f"Total: {total:.2f} €".
What it is not: concatenation with + and str() everywhere.
Reflex: displaying values = f-string.
- with open()
- Opens a file while guaranteeing its automatic closing: with open("f.txt", encoding="utf-8") as f:. Modes r (reads), w (overwrites), a (appends).
Simple example: Nabil reads the export with with and utf-8 encoding.
What it is not: an open() with no with and no encoding.
Reflex: file = with + encoding, always.
- CSV
- Text file where values are separated by commas: universal exchange format (spreadsheets). The csv module for clean reading/writing.
Simple example: Nabil cuts each export row with the csv module.
What it is not: manual cutting that breaks on quotes.
Reflex: spreadsheet to exchange = csv module.
- JSON
- Structured text format (keys/values), universal for data and APIs. The json module: json.load / json.dump, persistent lists and dicts.
Simple example: Nabil's summary survives a restart in JSON.
What it is not: a hand-made text file unreadable by other tools.
Reflex: structured data to keep = JSON.
In practice — In practice — Formatting text and reading/writing files
- Clean and transform a string: " rapport algiers ".strip().upper(), then cut a CSV row into fields with .split().
- Compose a report line with an f-string including a computation: f"Total : {prix * quantite:.2f} €" (2 decimals).
- Write text with "with open("notes.txt", "w", encoding="utf-8") as f" then read it back in "r" mode: check the automatic closing.
- Load/save a list of dicts in JSON with json.dump and json.load: observe persistence between two runs.
Script ready to say — explaining with to a colleague (30 s)
"For Nabil's report, everything goes through f-strings with :.2f — no more concatenation. And each file opens with with and utf-8 encoding: it closes by itself even if it crashes, and the accents of Algiers stay intact. The summary goes to JSON, it survives a restart."
Say it while showing a file left locked without with, then clean with with.
Copy-paste template — formatted report and JSON saving
import json
from pathlib import Path
lignes = [" algiers,1650 ", "oran,800", " algiers,450 "]
total = 0.0
for ligne in lignes:
wilaya, montant = [c.strip() for c in ligne.split(",")]
total += float(montant)
print(f"{wilaya.title():<10} : {float(montant):.2f} €")
print(f"{'Total':<10} : {total:.2f} €")
Path("total.json").write_text(json.dumps({"total": total}, ensure_ascii=False, indent=2), encoding="utf-8")
Report line
Each of Nabil's sales becomes f"{wilaya} : {montant:.2f} €" — computation and formatting in one line.
Names to normalize
Wilayas typed loosely go through strip().lower(): "Algiers" and "algiers" no longer duplicate.
CSV export to re-read
The month's export is cut with split/join via the csv module: fields and quotes handled cleanly.
Summary to keep
The monthly total goes to JSON with ensure_ascii=False: accents intact, re-read the next month.
✓ Best practices
- Use f-strings everywhere, with built-in formatting (:.2f, :.0%).
- Systematically clean inputs (strip, lower) before comparing.
- Open with with + encoding and prefer pathlib + JSON to persist.
✗ Common pitfalls
- Concatenating with + instead of an f-string (unreadable, type errors).
- Opening without with (file left unclosed on error).
- Forgetting encoding="utf-8" (broken accents) or opening in "w" a file to keep (overwritten).
Key takeaways
- String methods (upper, strip, split, join, replace) transform text (a new string is returned).
- f-strings (f"...{var}...") are the modern, readable way to format, with :.2f and :.0%.
- with open(..., encoding="utf-8") reads/writes while closing automatically; r reads, w overwrites, a appends.
- CSV and JSON are the exchange formats (csv and json modules); pathlib for paths.
48h challenge — apply tomorrow
Take a real text file (Nabil's or yours): clean it (strip/split/join), display a summary in f-strings with :.2f, save it in JSON via with + encoding and re-read it after a restart.
- I can format with f-strings (variables, computations, :.2f).
- I can clean with strip/split/join and test with in.
- I have read and written a file with with + encoding, and persisted in JSON.
Frequently asked questions
Why prefer f-strings over concatenation with +?
f-strings are more readable, avoid type errors (no need to convert numbers to str), and allow built-in formatting (:.2f for 2 decimals, alignment, percentages). It is the officially recommended method since Python 3.6.
What is the difference between the "r", "w" and "a" modes of open()?
"r" reads (default), "w" writes by OVERWRITING existing content, "a" appends at the end without erasing. Warning: opening an existing file in "w" deletes its content immediately — hence the value of reading before writing.
What does the "with open(...)" block guarantee?