Input & Output
Format text with f-strings, read and write files safely with with, and save structured data as JSON.
You will learn
- f-strings and the format mini-language: width, alignment, decimals, thousands separators, and
=for debugging. str.format(), manual padding, and the old%style (to read older code).- The difference between
str()andrepr(), plus a first look at 3.14's template strings. - Opening, reading and writing files with
with open(...), and why you passencoding. - Saving and loading data with the
jsonmodule.
Formatted string literals (f-strings)
Put an f before the opening quote, and write any Python expression inside { }. The expression is evaluated and its value inserted. This is the modern, preferred way to build strings.
π¨ JS: f-strings are template literals, `${x}`. β Java: they replace String.format("%d items", n) and + chains.
The format specifier: {value:spec}
After a colon you can add a format specifier that controls how the value looks. The general shape is [fill][align][width][,][.precision][type].
| Spec | Meaning | Example | Result |
|---|---|---|---|
.2f | fixed-point, 2 decimals | f"{3.14159:.2f}" | '3.14' |
10 | minimum width 10 | f"{'ab':10}|" | 'ab |' |
< > ^ | align left / right / centre | f"{'ab':>6}" | ' ab' |
*^7 | fill with *, centred, width 7 | f"{'ab':*^7}" | '**ab***' |
, or _ | thousands separator | f"{1234567:,}" | '1,234,567' |
03d | integer, zero-padded to 3 | f"{7:03d}" | '007' |
.1% | percentage | f"{0.256:.1%}" | '25.6%' |
e | scientific notation | f"{12345.678:.2e}" | '1.23e+04' |
x b | hexadecimal / binary | f"{255:x} {5:b}" | 'ff 101' |
Conversions !r and !s, and the = debugging specifier
!r uses repr() (see below), which shows quotes around strings, and !s uses str(). A trailing = prints the expression text and its value, which is perfect for quick debugging.
π‘ Tip: since Python 3.12 you can reuse the same quote type inside an f-string expression: f"{d["key"]}" works. To write a literal brace, double it: f"{{not a field}}".
str() vs repr()
str(x) gives a readable form for humans, and print() uses it. repr(x) gives an unambiguous form for developers, often valid Python, and the REPL uses it to echo values.
Template strings (t-strings)
Python 3.14 added template strings, written with a t prefix (PEP 750). They look like f-strings, but they don't produce a str. They produce a Template object that keeps the text parts and the values separate. Libraries use them to process the values safely, for example to escape HTML or SQL, before joining everything together.
You won't write t-strings often as a beginner, but you'll meet them in web and database libraries. For everyday output, use f-strings.
The str.format() method
Before f-strings (3.6), people used str.format(). It is still useful when the template is stored separately from the values, for example in a config file or a translation table.
Format specs after the colon work exactly as in f-strings.
Manual string formatting
String methods can pad text to a width: rjust(), ljust(), center() and zfill(). They never cut a string that is too long.
Old string formatting (%)
π Older syntax you'll still see: the % operator, similar to C's printf. It is common in older code and in the logging module (Chapter 14). Don't use it in new code.
Reading from the keyboard: input()
input(prompt) shows the prompt, waits for the user to press Enter, and returns what they typed as a string, without the newline. Convert it yourself if you need a number.
Reading and writing files
open(filename, mode, encoding=...) returns a file object. Always open files in a with block. It closes the file automatically when the block ends, even if an error happens inside.
Analogy: with is a door that closes itself behind you, whatever way you leave the room.
| Mode | Meaning | If the file exists⦠| If it doesn't⦠|
|---|---|---|---|
'r' (default) | read text | reads it | FileNotFoundError |
'w' | write text | empties it first! | creates it |
'a' | append text | adds to the end | creates it |
'x' | exclusive create | FileExistsError | creates it |
'b' (added: 'rb', 'wb') | binary: bytes, not str | used for images, zip files and other non-text data | |
'+' (added: 'r+') | read and write | rarely needed | |
Methods of file objects
| Method | Does |
|---|---|
f.read() / f.read(n) | the whole rest of the file, or at most n characters. Returns '' at the end of the file. |
f.readline() | one line, including its \n. Returns '' at the end of the file. |
for line in f: | loops over the lines (the best way to read text files) |
f.readlines() / list(f) | all lines as a list |
f.write(s) | writes a string and returns the number of characters written |
f.tell() / f.seek(pos) | the current position / jump to a position |
β File may never be closed
β Always closed
β οΈ Pitfall: the encoding. Since 3.15, text files default to UTF-8 on every system (PEP 686). Older Pythons used the system's locale encoding, which on Windows was often cp1252, and your code may still run on those versions. Writing encoding="utf-8" explicitly makes your code portable and clear. In binary mode ('rb'), don't pass an encoding at all.
π‘ Tip: the pathlib module (Chapter 13) has handy shortcuts: Path("a.txt").read_text(encoding="utf-8") and Path("a.txt").write_text(s, encoding="utf-8").
Saving structured data with json
Writing numbers and lists as text by hand gets messy fast. JSON (JavaScript Object Notation) is the standard text format for data exchange, and the json module converts between Python objects and JSON text.
json.dumps(obj)β a JSON string.json.loads(s)β a Python object. (The s stands for string.)json.dump(obj, f)β writes to a file.json.load(f)β reads from a file.
| Python | JSON | Back to Python |
|---|---|---|
dict | object {} | dict |
list, tuple | array [] | list (a tuple comes back as a list!) |
str | string | str |
int, float | number | int / float |
True / False | true / false | True / False |
None | null | None |
β οΈ Pitfalls: JSON object keys are always strings, so {1: "a"} comes back as {"1": "a"}. Sets, dates and your own classes can't be serialised directly (you get a TypeError); convert them to lists, strings or dicts first.
β Never unpickle untrusted data. The pickle module can save almost any Python object, but loading a pickle can run arbitrary code. Use JSON for data that comes from outside your program.
More examples
1. A neatly aligned receipt
2. Counting lines and words in a file
3. A settings file with defaults
4. Appending a log line with a timestamp
5. Copying a binary file in chunks
Recap
- f-strings use
f"{expr:spec}":.2f, width,< > ^,,,%,!r, and{x=}for debugging. str()is for people andrepr()is for developers.str.formatsuits stored templates;%is legacy. t-strings (3.14) buildTemplateobjects for libraries.- Use
with open(path, mode, encoding="utf-8") as f:. Mode'w'erases the file,'a'appends,'x'refuses to overwrite. - Loop over a file to read it line by line.
write()doesn't add newlines. json.dump/loadwork with files anddumps/loadswith strings. Tuples become lists and keys become strings. Never unpickle untrusted data.
Exercises
Exercise 1 π’ easy predict the output
What do these produce? f"{7.256:.1f}", f"{42:5}|", f"{'x':<3}|", f"{1500000:,}"
Show solution
'7.3', ' 42|' (numbers align right by default), 'x |' (strings align left by default), and '1,500,000'.
Exercise 2 π’ easy refactor it
Rewrite with an f-string: "Hello " + name + ", you are " + str(age) + " years old"
Show solution
f-strings convert values for you, so there is no need for str() or +.
Exercise 3 π’ easy fix the bug
The file ends up as one long line. Fix it.
Show solution
Unlike print, write() writes exactly what you give it and adds no newline.
Exercise 4 π’ easy explain why
A script opens report.txt with mode 'w' to "check what's in it", and the report is now empty. Why? Which mode should it use?
Show solution
Mode 'w' truncates (empties) the file the moment it is opened, before anything is read. To read, use 'r', which is the default. To add to the file, use 'a'.
Exercise 5 π‘ medium predict the output
What does this print?
Show solution
The tuple became a list and the integer key 2 became the string '2', so the round trip is not equal.
Exercise 6 π‘ medium build it
Print a table of the numbers 1β5 with their squares and square roots, with the columns aligned and the roots shown to 3 decimals.
Show solution
Giving every row the same width per column keeps the columns lined up.
Exercise 7 π‘ medium build it Β· playground
Create playground/storage.py with save(expenses, path) and load(path) using JSON. load should return [] if the file doesn't exist yet. Test a round trip.
Show solution
Chapter 10 replaces the exists check with try/except FileNotFoundError. That version is more robust, because the file could disappear between the check and the open.
Exercise 8 π‘ medium fix the bug
This crashes with TypeError: Object of type set is not JSON serializable. Fix it.
Show solution
JSON has no set type. Converting to a sorted list also makes the output the same on every run.
Exercise 9 π΄ hard predict the output
What does this print?
Show solution
'one\n' 'two\nthree' ''. readline keeps the newline. read() continues from the current position. At the end of the file, read() returns an empty string, not an error.
Exercise 10 π΄ hard mini-project Β· playground
Create playground/csv_to_json.py. It reads expenses.csv (lines like 2026-10-01,food,12.50,lunch), skips blank lines and lines starting with #, converts amounts to float, writes expenses.json, and prints a formatted summary of the count and total. (Chapter 13 shows the csv module; here, split the lines by hand.)
Show solution
split(",", 3) keeps any commas inside the note. It combines file reading, string cleaning, JSON writing and f-string formatting.