, ,

Save and Read Files So Your Data Survives (Python for Beginners, Part 11)

Your program forgets everything when it closes. Learn to read and write files in Python with open, the with block, and the csv module, then make your expense tracker remember its data.

Python for Beginners · Part 11 of 20

Run this, close Python, then run it again. The count is back to zero every single time.

python
count = 0
count = count + 1
print('expenses saved:', count)
output
expenses saved: 1

Nothing you put in a variable outlives the program. When it stops, the memory it used is handed back and your data is gone. A real tool has to remember. That memory lives in a file on disk, and by the end of this part your expense tracker keeps its data after you quit.

The short versionTo save, open a file in write mode and call .write(). To load, open it in read mode and call .read(), or step through it one line at a time. Always open a file with a with block so Python closes it for you. For rows of data like your expenses, skip hand made text and use the csv module.

This part is for the reader building along from the start, and equally for an IT person who needs a script to read and write data. It assumes Part 10, where you formatted a clean receipt with f-strings. You need Python 3 and about twenty five minutes. Every snippet runs as printed, and you can copy it and run it as is. One small warning that matters the moment you copy code with quotes in it: a webpage sometimes turns straight quotes into curly ones, and Python rejects a curly quote with a SyntaxError. If a copied line breaks, retype the quotes yourself.

Writing your first file

Opening a file gives you a handle you can read from or write to. The clean way to do it is a with block. You open the file, do your work inside the indented block, and Python closes the file the moment the block ends, even if something goes wrong partway.

python
with open('note.txt', 'w',
          encoding='utf-8') as f:
    f.write('first line\n')
    f.write('second line\n')
print('done')
output
done

Three things are happening. The 'w' means write mode. The encoding='utf-8' tells Python how to turn text into bytes, and utf-8 is the sane default that handles every language and symbol. The n at the end of each string is a newline, because .write() puts down exactly the characters you give it and nothing more. Leave the n out and both lines run together.

your program note.txt on disk .write() saves .read() loads

Figure 1. Writing sends text out to a file. Reading pulls it back on the next run.

Reading it back

Open the same file in read mode, which is the default, so you can leave the mode out. The simplest read pulls the whole file into one string with .read().

python
with open('note.txt',
          encoding='utf-8') as f:
    text = f.read()
print(text)
output
first line
second line

For a big file you rarely want it all at once. Looping over the file hands you one line at a time, which stays light no matter how large the file grows. Each line still carries its trailing newline, so .rstrip() trims it off for a clean print.

python
with open('note.txt',
          encoding='utf-8') as f:
    for line in f:
        print(line.rstrip())
output
first line
second line

That trailing newline is the single most common surprise with files. If your printed lines have odd blank gaps, or a comparison like line == 'done' never matches, the leftover n is the reason. Strip it and the problem disappears.

The three modes you actually need

The letter you pass to open() decides what it will let you do, and what it does to a file that is already there. Getting this wrong is how beginners lose data, so it is worth a table.

ModeOpens forIf the file existsIf it is missing
'r'readingreads itraises an error
'w'writingwipes it firstcreates it
'a'appendingadds to the endcreates it
'x'writingrefuses, raises an errorcreates it

Table 1. The open modes, and what each one does to an existing file.

Gotcha: write mode wipes the fileOpening in 'w' empties the file the instant it opens, before you write a single thing. Point 'w' at a file you care about and its old contents are gone. When you want to add to a file instead of replacing it, use 'a':
python
with open('log.txt', 'a',
          encoding='utf-8') as f:
    f.write('one more line\n')
Run that five times and you get five lines. Run the 'w' version five times and you get one.
mode w new text only old lines gone mode a old kept, new added at end

Figure 2. Write mode starts blank. Append mode keeps what was there and adds after it.

When the file is not there

Try to read a file that does not exist and Python stops with a traceback. Read it the way Part 2 taught: last line first.

output
Traceback (most recent call last):
  File "read.py", line 1, in <module>
    open('missing.txt', encoding='utf-8')
FileNotFoundError: [Errno 2] No such
file or directory: 'missing.txt'

The last line names the problem plainly. FileNotFoundError means Python looked for that file in the folder your program is running from and did not find it. Usually the file name is misspelled, or it lives in a different folder than you think. This is a normal message, not a crash to fear, and it is telling you exactly what went wrong.

The fix for a tracker is to check before you read. If the save file is not there yet, start with an empty list instead of failing:

python
from pathlib import Path

path = Path('expenses.csv')
if path.exists():
    print('found it, loading')
else:
    print('no file yet, starting fresh')
output
no file yet, starting fresh

Paths that work on every operating system

Freshers write code on Windows, macOS and Linux, and file paths are one place they differ. Windows separates folders with a backslash, as in dataexpenses.csv. macOS and Linux use a forward slash, as in data/expenses.csv. If you hard code one style, your code breaks on the other machine.

The standard library solves this with pathlib. You build a path with the / operator and Python prints the right separator for whatever system runs it:

python
from pathlib import Path

data = Path('data') / 'expenses.csv'
print(data)
output
data/expenses.csv

On Windows that same code prints dataexpenses.csv. You wrote it once and it is correct everywhere. Path objects also carry handy checks like .exists() and readers like .read_text(), so you lean on them instead of gluing strings together by hand.

CSV, the right tool for rows of data

You could save each expense as a line of text and split it back on commas, the way Part 10 showed. It works until an expense note contains a comma, and then your split cuts the line in the wrong place and the data is wrong. The standard library ships a module built exactly for rows of fields, and it handles those edge cases for you. It is called csv.

Here is my honest opinion, and it runs against the advice you will see in tutorials: do not import pandas to read a ten line file. Pandas is a fine library for large tables and analysis, but pulling in a heavy dependency to save a handful of expenses is the wrong reach. The csv module comes with Python, needs no install, and is the right size for this job. Reach for the small tool that fits.

csv.DictWriter writes each dictionary as a row, and csv.DictReader reads each row back as a dictionary. You tell the writer the field names once:

python
import csv

rows = [
    {'amount': 120.5, 'category': 'food'},
    {'amount': 40, 'category': 'travel'},
]

with open('expenses.csv', 'w', newline='',
          encoding='utf-8') as f:
    fields = ['amount', 'category']
    writer = csv.DictWriter(f,
                            fieldnames=fields)
    writer.writeheader()
    writer.writerows(rows)
print('saved', len(rows), 'rows')
output
saved 2 rows

The newline='' looks odd but matters: it stops the csv module from leaving blank lines between rows on Windows. Always pass it when you open a file for csv. The file it writes is plain text you can open in any editor or a spreadsheet:

expenses.csv
amount,category
120.5,food
40,travel
open file read/write auto close the with block handles the first and last box for you

Figure 3. A with block opens the file, lets you work, and closes it even if an error happens.

Reading it back is the mirror image. DictReader uses the header row for the keys and hands you one dictionary per line:

python
import csv

with open('expenses.csv', newline='',
          encoding='utf-8') as f:
    reader = csv.DictReader(f)
    rows = list(reader)

for row in rows:
    print(row)
output
{'amount': '120.5', 'category': 'food'}
{'amount': '40', 'category': 'travel'}

Look closely at the amounts. They came back as text, '120.5' in quotes, not numbers. Every value read from a file arrives as a string. You convert with float() or int() before you do any maths. That conversion is exactly where things can go wrong if the file holds a typo, and handling that safely is the whole of Part 12.

Project check-in: expenses that survive a restartWrap the save and load steps in two functions, so your tracker writes its list to expenses.csv and reads it back next time it starts. This is the moment the project stops being a toy.
expenses.py
import csv
from pathlib import Path

FIELDS = ['amount', 'category']
PATH = Path('expenses.csv')

def save_expenses(items):
    with open(PATH, 'w', newline='',
              encoding='utf-8') as f:
        w = csv.DictWriter(f,
                           fieldnames=FIELDS)
        w.writeheader()
        w.writerows(items)

def load_expenses():
    if not PATH.exists():
        return []
    with open(PATH, newline='',
              encoding='utf-8') as f:
        return list(csv.DictReader(f))

expenses = load_expenses()
expenses.append(
    {'amount': 55, 'category': 'books'})
save_expenses(expenses)
print('now have', len(expenses), 'expenses')
output
now have 1 expenses
Run it again and the count climbs to 2, then 3. The data is on disk now, so it remembers between runs. That is real software behaviour.
Why this matters in your first jobAlmost every program you touch at work reads a config file, writes a log, or saves output somewhere. The with open(...) pattern and knowing that 'w' wipes while 'a' adds are daily tools, not trivia. An engineer who reaches for a with block by reflex and never leaves a file open looks like someone who has done this before. It is a small habit that shows up in code review on day one.
Real interview question"Why do we open files with a with statement instead of calling open() and close() ourselves?" The answer they want: the with block closes the file automatically when the block ends, even if an error is raised inside it, so you never leak an open file or lose unwritten data. A strong follow up is that a file left open can hold a lock or lose buffered writes. It is a quick way for them to see whether you understand cleanup, not just reading and writing.
Try it yourselfWrite two lines to a file called hello.txt, the words hello and world, each on its own line. Then read the file back and print it. Expected output:
expected output
hello
world
Show solution
python
with open('hello.txt', 'w',
          encoding='utf-8') as f:
    f.write('hello\n')
    f.write('world\n')

with open('hello.txt',
          encoding='utf-8') as f:
    print(f.read().rstrip())

Write mode with two .write() calls, each ending in n, then a second block to read it back. The .rstrip() trims the final newline so there is no trailing blank line.

File questions that come up early

Where does the file get saved if I only give a name? In the folder your program is running from, called the working directory. Give a full path, or build one with pathlib, when you want it somewhere specific.

Do I have to close the file myself? Not if you use a with block, which closes it for you. If you ever call open() on its own, you are responsible for calling .close(), which is one more reason to prefer with.

What is the difference between .read() and looping over the file? .read() pulls the entire file into memory as one string. Looping hands you one line at a time and stays light, which is what you want for anything large.

Why did my numbers come back as text? Everything read from a file is a string. Convert with int() or float() before doing maths. This is normal and expected.

Should I use CSV or JSON to save data? CSV suits flat rows like a table. JSON suits nested data with lists inside dictionaries. Your tracker is flat rows, so CSV fits now. Part 16 shows JSON for when the shape gets richer.

You can nowYou can now save data to a file and read it back, choose the right open mode, avoid the write mode trap that wipes a file, build paths that work on any operating system, and store your expenses as CSV so they survive a restart.

Your tracker finally remembers. Next comes the part that keeps it from falling over when someone types nonsense into it: reading errors, catching them, and keeping the program alive. Save your expenses.py and bring it to Part 12.

Python for Beginners · Part 11 of 20
« Previous: Part 10  |  Complete Guide  |  Next: Part 12

References

Python docs: Reading and writing files
Python docs: csv module
Python docs: pathlib

About The Author


Discover more from Journal of Intelligent Infrastructure – By Dr Pranay Jha

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure - By Dr Pranay Jha

Subscribe now to keep reading and get access to the full archive.

Continue reading