Skip to content
aviral gupta

// B5.3 · ~25 min · Beginner

Reading and writing files

After this lesson you can save text to a file and read it back: pick the right mode, keep the file inside a with block, and handle the newline at the end of every line.

Lesson 3 of 5 in B5 Modules, files and errors

You will be able to

  • Open a file with the right mode (r, w or a) inside a with block, with encoding="utf-8"
  • Read a whole file with read(), or line by line with a for loop, removing the newlines
  • Write text with write(), adding the newlines and converting numbers yourself
  1. Warm-up · Activity 1 of 7

    Warm-up from the last lesson: what does this print?

    print(f"{'pen':<5}|{3:>3}|")
  2. Predict · Activity 2 of 7

    Predict before you read on: what does this print?

    with open("notes.txt", "w", encoding="utf-8") as f:
        f.write("one")
        f.write("two")
    
    with open("notes.txt", encoding="utf-8") as f:
        print(f.read())
  3. Practice · Activity 3 of 7

    Each pass of the loop opens the file again. Fill in the mode so that both words end up in log.txt.

    for word in ["red", "green"]:
        with open("log.txt", "____", encoding="utf-8") as f:
            f.write(word + "\n")
    open("log.txt", "", encoding
  4. Practice · Activity 4 of 7

    Now with "w". What does this print?

    for word in ["red", "green"]:
        with open("log.txt", "w", encoding="utf-8") as f:
            f.write(word + "\n")
    
    with open("log.txt", encoding="utf-8") as f:
        print(f.read().split())
  5. Practice · Activity 5 of 7

    Match each mode to what open does with it.

  6. Brain teaser · Activity 6 of 7

    Brain teaser. What does this print?

    with open("names.txt", "w", encoding="utf-8") as f:
        f.write("Ada\nGrace\n")
    
    with open("names.txt", encoding="utf-8") as f:
        lengths = [len(line) for line in f]
    
    print(lengths)
  7. Apply · Activity 7 of 7

    Mini-task. Write three tasks, buy milk, call Ada and water plants, into todo.txt, one per line. Then open the file again, read it line by line and print each task numbered: 1. buy milk. Run the program twice: the list must not grow.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A shopping list on disk

The program writes a shopping list with "w", adds one more item with "a", and reads the file twice: once line by line to number the items, and once with read() to look at the raw text. repr shows the newlines that write put into the file.

main.py

items = ["apples", "bread", "cheese"]

with open("shopping.txt", "w", encoding="utf-8") as f:
    for item in items:
        f.write(item + "\n")

with open("shopping.txt", "a", encoding="utf-8") as f:
    written = f.write("dates\n")
print("appended", written, "characters")

with open("shopping.txt", encoding="utf-8") as f:
    for number, line in enumerate(f, start=1):
        print(f"{number}. {line.strip()}")

with open("shopping.txt", encoding="utf-8") as f:
    text = f.read()
print(repr(text[:13]), len(text.splitlines()), "lines")

Run it with

python main.py

Output

appended 6 characters
1. apples
2. bread
3. cheese
4. dates
'apples\nbread\n' 4 lines
  • write returned 6: the five letters of dates plus the newline.
  • "a" kept the three items written with "w" and added dates at the end.
  • line.strip() removes the newline, so the numbered lines have no blank lines between them.
  • repr(text[:13]) shows the first two lines as they are stored, each ending in \n.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Exercises

Exercise 1 of 2

Scores from a file

scores.txt (second tab) holds one name,score pair per line. Read it and print two lines: how many scores there are and their average with one decimal place, then the best name and score. For the given file: 3 scores, average 82.3 and best: ada (90). The tests also try other files, so do not copy the numbers.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    with open("scores.txt", encoding="utf-8") as f: then for line in f: gives one line at a time.

  2. Hint 2

    line.strip().split(",") turns "ada,90\n" into ["ada", "90"]. Convert the score with int().

  3. Hint 3

    Collect the pairs in a dict. max takes a key= just as sorted() does in lesson B3.4, so max(scores, key=lambda name: scores[name]) gives the name with the highest score, and {average:.1f} rounds to one decimal.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

scores: dict[str, int] = {}
with open("scores.txt", encoding="utf-8") as f:
    for line in f:
        name, score = line.strip().split(",")
        scores[name] = int(score)

average = sum(scores.values()) / len(scores)
best = max(scores, key=lambda name: scores[name])
print(f"{len(scores)} scores, average {average:.1f}")
print(f"best: {best} ({scores[best]})")
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

# Read scores.txt: one "name,score" pair per line.
print("3 scores, average 82.3")
print("best: ada (90)")

test_main.py

from learnrun import run_main


def run_with(text):
    with open("scores.txt", encoding="utf-8") as f:
        original = f.read()
    with open("scores.txt", "w", encoding="utf-8") as f:
        f.write(text)
    try:
        return run_main().strip().splitlines()
    finally:
        with open("scores.txt", "w", encoding="utf-8") as f:
            f.write(original)


def test_given_file():
    """The given file gives 3 scores, average 82.3, best ada"""
    got = run_main().strip().splitlines()
    assert got == ["3 scores, average 82.3", "best: ada (90)"], f"the program printed {got!r}"


def test_other_file():
    """Another file gives its own count, average and best"""
    got = run_with("lin,70\nmo,95\n")
    assert got == ["2 scores, average 82.5", "best: mo (95)"], f"for lin,70 and mo,95 the program printed {got!r}"


def test_no_final_newline():
    """A file whose last line has no newline still works"""
    got = run_with("kim,60\nsam,61")
    assert got == ["2 scores, average 60.5", "best: sam (61)"], f"for kim,60 and sam,61 the program printed {got!r}"

scores.txt

ada,90
grace,85
alan,72

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Exercise 2 of 2

Save and load a list of lines

Write save_lines(path, lines), which writes each string of the list to the file on its own line, replacing what was there, and load_lines(path), which returns the lines as a list without their newlines. save_lines("todo.txt", ["milk", "eggs"]) makes the file text exactly "milk\neggs\n". Use with and encoding="utf-8" in both.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    Mode "w" empties the file first, so a second save replaces the first one. "a" would add to it.

  2. Hint 2

    write adds no newline: write line + "\n" for each item.

  3. Hint 3

    When reading, remove the newline from each line: [line.rstrip("\n") for line in f]. strip() also works, but it would remove spaces at the ends too.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

def save_lines(path: str, lines: list[str]) -> None:
    with open(path, "w", encoding="utf-8") as f:
        for line in lines:
            f.write(line + "\n")


def load_lines(path: str) -> list[str]:
    with open(path, encoding="utf-8") as f:
        return [line.rstrip("\n") for line in f]


if __name__ == "__main__":
    save_lines("todo.txt", ["milk", "eggs"])
    print(load_lines("todo.txt"))
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

def save_lines(path: str, lines: list[str]) -> None:
    with open(path, "a", encoding="utf-8") as f:
        for line in lines:
            f.write(line)


def load_lines(path: str) -> list[str]:
    with open(path, encoding="utf-8") as f:
        return list(f)


if __name__ == "__main__":
    save_lines("todo.txt", ["milk", "eggs"])
    print(load_lines("todo.txt"))

test_main.py

from main import load_lines, save_lines


def test_file_text():
    """save_lines writes one line per item, each ending in a newline"""
    save_lines("t1.txt", ["milk", "eggs"])
    with open("t1.txt", encoding="utf-8") as f:
        text = f.read()
    assert text == "milk\neggs\n", f"the file holds {text!r}, expected 'milk\\neggs\\n'"


def test_round_trip():
    """load_lines gives back exactly what was saved, umlauts included"""
    lines = ["Grüße", "tea for two", "42"]
    save_lines("t2.txt", lines)
    got = load_lines("t2.txt")
    assert got == lines, f"load_lines returned {got!r}, expected {lines!r}"


def test_replaces():
    """Saving again replaces the old lines"""
    save_lines("t3.txt", ["old"])
    save_lines("t3.txt", ["new"])
    got = load_lines("t3.txt")
    assert got == ["new"], f"after saving ['old'] and then ['new'], load_lines returned {got!r}"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Common mistakes

Reading a file that does not exist

with open("scores.txt", encoding="utf-8") as f:
    print(f.read())

What Python prints

No such file or directory: 'scores.txt'

Why, and the fix

The full line is FileNotFoundError: [Errno 2] No such file or directory: 'scores.txt' (in the browser the number is 44). Mode "r" never creates a file. Check the spelling, and that the file is in the folder the program runs in; on your computer that is the folder you ran python from.

Writing a number

count = 42
with open("count.txt", "w", encoding="utf-8") as f:
    f.write(count)

What Python prints

TypeError: write() argument must be str, not int

Why, and the fix

write only takes strings, unlike print. Convert first, and add the newline yourself: f.write(str(count) + "\n") or f.write(f"{count}\n"). When you read it back, int(line) turns the text into a number again.

Writing to a file opened for reading

with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("first line\n")

with open("notes.txt", encoding="utf-8") as f:
    f.write("second line\n")

What Python prints

io.UnsupportedOperation: not writable

Why, and the fix

Without a mode, open uses "r", which only reads. To add a line, open with "a"; to replace the content, open with "w".

Python in the browser: Pyodide 314.0.7, MPL-2.0. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

open, modes and with

open(name, mode, encoding="utf-8") gives you a file object. The mode says what you will do: "r" reads and is the default; "w" writes and first empties the file, creating it if needed; "a" appends at the end and keeps what is there. Always pass encoding="utf-8", or the encoding depends on the computer, and "Grüße" may not survive. Open files in a with block: when the block ends, the file is closed, even if an error happened inside it.

Reading: read() or a loop

f.read() returns the whole file as one string; a second call returns "", because the end of the file has been reached. for line in f: gives you one line at a time, which also works for huge files. Each line keeps its newline "\n" at the end, except possibly the last one, so print(line) would show blank lines between them. line.strip() removes the newline, and spaces, from both ends. text.splitlines() splits a whole string into lines without the newlines.

Writing: write() adds nothing for you

f.write(text) writes exactly the string you pass and returns how many characters it wrote. Unlike print, it adds no newline, so two writes of "one" and "two" give onetwo. Add "\n" yourself at the end of each line. write only takes strings: convert a number first with str(n) or an f-string. In the browser, files live in memory and every run starts with only the files shown in the tabs; on your own computer they are real files.

Sources

Last reviewed September 29, 2026