2.2 · Strings, formatting (f-strings) & files

Level 2 · Data structures & OOP

2.2Strings, formatting (f-strings) & files

Goal: Format text and read/write files (txt, CSV, JSON).
Estimated time: 14 min · self-paced reading · If you only have 5 min: read “The essentials in 30s” + the diagram.

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.

Common misinterpretation: "Concatenating with + and opening without with works just as well". No. Concatenation with + is unreadable as soon as you mix text and numbers (and raises TypeError without conversion), with no built-in formatting; open() without with leaves the file open on error (leak, lock) and without encoding breaks accents. Nabil's rule: f-string for displaying, split/join/strip for transforming, with + encoding for keeping — three moves, zero surprises.
Text and files Display, transform, keep f-string (display) Variablesand :.2f built insplit/join (transform) Cut,assemble, cleanwith+JSON (keep) Closes byitself, encoding, persistent
Display, transform, keep
Text version of the diagram (SEO / screen readers / translation): 1) Nabil DISPLAYS with f-strings: variables and computations inside braces, :.2f for amounts, :.0% for shares — neither % nor concatenation. 2) He TRANSFORMS with split (cut), join (assemble), strip/lower/replace (clean, normalize) and in for testing. 3) He KEEPS with with open(..., encoding="utf-8") which closes by itself, r/w/a modes distinguished, JSON for structure and pathlib for paths. Lesson: display, transform, keep — with modern tools.

🔎 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

  1. Clean and transform a string: " rapport algiers ".strip().upper(), then cut a CSV row into fields with .split().
  2. Compose a report line with an f-string including a computation: f"Total : {prix * quantite:.2f} €" (2 decimals).
  3. Write text with "with open("notes.txt", "w", encoding="utf-8") as f" then read it back in "r" mode: check the automatic closing.
  4. Load/save a list of dicts in JSON with json.dump and json.load: observe persistence between two runs.
You format text cleanly with f-strings and you read/write txt, CSV and JSON files: your programs keep a memory.

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.

Section exit checklist
  • 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.
Self-assessment 1-5: do I use f-strings instead of +?is every open() in a with with encoding?

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.

Check your understanding

What does the "with open(...)" block guarantee?

Additional resources (optional, to go further)

Level 2 examFresh, harder questions to test what you've learned