Lesson 3.3Lesson 3.3 · Functions, Modules & Files
Reading & Writing Files
Data lives in files - learn to open, read and write them and your scripts finally touch the real world
A script that only prints to the screen is a toy. The moment it reads a file and writes one back, it starts doing real work.
Everything a designer's data lives in - a room list, an exported schedule, a settings note, a batch of drawings - is a file on disk. Until your script can open those files, read what is inside and write results back out, it is sealed off from the actual work.
This lesson is the plumbing. It is not glamorous, but reading and writing files is the foundation every later lesson stands on: the CSV and JSON of the next lesson, the pandas tables of Module 4, the file-automation of Module 8. Learn open(), the with block, and how to point at files safely with paths, and your scripts step out of the sandbox and into your real project folders.
with open(path, mode) as f. r read, w overwrite, a append. pathlib for paths. Read in, write out.
open() and the with block
To work with a file you first open it. Python's open() gives you a file object, and the modern, safe way to use it is inside a with block, which guarantees the file is closed again the moment you are done - even if something goes wrong midway:
with open("rooms.txt") as f:
text = f.read()
print(text)That small with line is doing real work. Opening a file reserves a resource from the operating system, and leaving files open causes subtle bugs - lost data, locked files, hitting limits. The with statement (a context manager) hands you the file as f, lets you work inside the indented block, and closes it automatically at the end. You could call f.close() yourself, but people forget, and forgetting bites. The habit to build from day one is simple: always open files with `with`. Everything else in this lesson happens inside that block - it is the frame around every file operation you will write.
It is worth being clear about what open() actually gives you, because the mental model prevents a lot of confusion. It does not read the file or load its contents into a variable; it returns a file object - a live connection to the file that you then send instructions through, like .read() or .write(). Think of it as opening a document rather than photocopying it: you now have it open in front of you and can act on it, but you have not yet taken anything out. That connection is a real operating-system resource, which is exactly why closing matters and why with exists. One more early note: open() takes a second argument, the mode, which says whether you intend to read, write or append. Leave it off and you get read mode by default - safe, because reading never changes a file. We will use the writing modes in a moment, but every file operation, read or write, begins with this same open() inside a with.
Always use with open(...) as f: - it closes the file for you, even on error.
Reading text: whole, lines, or one at a time
Once a file is open for reading you have choices about how much to pull in. Three patterns cover nearly everything:
with open("rooms.txt") as f:
whole = f.read() # everything as one string
with open("rooms.txt") as f:
lines = f.readlines() # a list, one string per line
with open("rooms.txt") as f:
for line in f: # one line at a time - memory-friendly
print(line.strip()) # strip() removes the trailing newlineread() gives you the entire contents as a single string - fine for small files. readlines() gives a list where each item is one line. Looping directly over the file, for line in f, reads one line at a time without loading the whole file into memory, which matters for large exports. Notice line.strip(): text files store an invisible newline character at the end of each line, and strip() cleans it (and stray spaces) off. This is exactly the kind of small, real detail that trips beginners - the data looks right but has hidden \n characters. Reading files is where your loops and strings from earlier modules finally meet real project data.
Which pattern to choose comes down to size and intent. For a small settings file or a short list, read() is simplest - grab the lot and work with it. When you want to process a file record by record, looping directly over the file object (for line in f) is both the most readable and the most memory-efficient choice, because Python hands you one line at a time instead of loading a possibly huge export all at once. That efficiency is not academic: a BIM or point-cloud export can be hundreds of megabytes, and reading it line by line is the difference between a script that runs and one that exhausts your machine's memory. A common shape you will write again and again is to loop the lines, strip() each, skip blanks or a header row, and do something with the rest - a pattern that leads directly into reading real schedules in the next lesson. Everything you learned about strings - split(), slicing, .startswith() - now has real text to work on.
read() = one big string. readlines() = list of lines. for line in f = one at a time. strip() the newline.
Writing and appending
Writing is the mirror image, but you must say how you want to open the file - the mode. The default is "r" for reading; "w" opens for writing and overwrites the file, while "a" appends to the end:
rooms = ["living 18.0", "kitchen 12.0", "bedroom 14.0"]
with open("schedule.txt", "w") as f:
for room in rooms:
f.write(room + "\n") # you add the newline yourself
with open("schedule.txt", "a") as f:
f.write("study 9.0\n") # added to the end, nothing lostTwo things catch everyone at first. First, "w" is destructive - it empties the file before writing, so a script pointed at the wrong filename can wipe real work; reach for "a" when you mean to add. Second, write() does not add line breaks for you the way print() does - if you want each room on its own line you append "\n" yourself, as above. Get those two habits right and writing files is genuinely easy. From here your scripts can produce real artefacts - a schedule, a report, a cleaned data file - that other people and other programs can open.
Because "w" is destructive, it is worth adopting one safety habit early: when a script overwrites data you care about, write to a new filename first, open the result, confirm it is right, and only then replace the original by hand. A wrong path or a stray "w" cannot then erase work you cannot recover. For building up a file across several runs - a log of what a batch script processed, say - "a" is exactly right, since each run adds to the end without disturbing what came before. And if you are writing many lines at once, you do not have to call write() repeatedly; f.writelines(list_of_strings) writes them in one go (though, like write(), it adds no newlines for you, so build them into the strings). None of this is complicated, but respecting the modes - read when you only look, append when you add, and write with care because it wipes - is what keeps file automation from ever costing you real data.
Mode matters: r read, w overwrite (!), a append. write() needs your own newline.
Paths: telling Python where the file is
open("rooms.txt") only works if the file sits where the script is run from. Real projects have folders, so you need to describe paths. A path can be relative (data/rooms.txt, starting from the current working directory) or absolute (/Users/you/project/data/rooms.txt, starting from the disk root). Building paths by gluing strings together with slashes is fragile and breaks across operating systems; the standard library gives you two better tools:
from pathlib import Path
data_dir = Path("data")
rooms = data_dir / "rooms.txt" # joins with the right separator
print(rooms.exists()) # True or False
print(rooms.suffix) # .txt
for csv_file in data_dir.glob("*.csv"):
print(csv_file.name) # every CSV in the folderThe modern pathlib module treats a path as an object: you join parts with /, and you get handy methods like .exists(), .name, .suffix and .glob() for free. The older os and os.path module does the same job in a more procedural style (os.path.join, os.listdir) and you will still see it everywhere. Either way, the lesson is the same: never hand-build paths with string concatenation - let pathlib or os handle the separators, and your scripts run the same on any machine. Getting comfortable with paths is what lets a script reach across a real project's folders instead of only the one it lives in.
The single most common path bug for beginners is the working directory surprise: open("rooms.txt") looks for the file not where the script lives but where you run it from, and those are often different - which is why a script works in your editor and then fails from the terminal. Two habits defuse this. First, prefer paths built relative to a known anchor rather than bare filenames. Second, when in doubt, check: Path("rooms.txt").resolve() prints the absolute path Python is actually looking at, instantly showing you where it thinks it is. pathlib's other conveniences earn their keep in real automation too - .glob("*.csv") finds every matching file in a folder for batch work, .mkdir(exist_ok=True) creates an output folder if it is missing, and .stem, .suffix and .name pull a filename apart cleanly so you can rename or re-extension files in a loop. These are the exact tools Module 8 uses to automate whole folders; getting fluent with them now means that later work is just more of a pattern you already know.
Relative starts from where you run; absolute from the root. Use pathlib's / to join, not string glue.
Putting it together: a tiny read-transform-write tool
With open, read, write and paths in hand you can already build the classic shape of a useful script - read a file in, do something in the middle, write a new file out. Here is a complete little tool that reads a rooms list, keeps only the larger rooms, and writes the result to a new file:
from pathlib import Path
src = Path("data") / "rooms.txt" # lines like: living 18.0
kept = []
with open(src) as f:
for line in f:
name, area = line.split() # split on whitespace
if float(area) >= 14.0: # a rule: large rooms only
kept.append(line.strip())
with open("large_rooms.txt", "w") as f:
f.write("\n".join(kept) + "\n")
print(f"Kept {len(kept)} large rooms")Nothing here is new - it is a loop, a decision, some string work and two with blocks - but together they read real data off disk, apply a design rule, and leave a real file behind. That input-process-output pattern is the backbone of almost every practical script you will write. What changes in the next lesson is only the format: instead of loose text you will read structured CSV schedules and JSON settings, using the same open-and-loop foundation you just built.
Read in, transform in the middle, write out. Same pattern every useful script uses.
open()
Getting a file object to read or write
Takes a path and a mode; returns a file you operate on. Pair it with with.
with (context manager)
Auto-closing a file after its block
Guarantees the file closes even on error - the safe default for all file work.
file modes
'r' read, 'w' overwrite, 'a' append
'w' is destructive - it empties the file first. Use 'a' to add without losing what is there.
pathlib.Path
Object-oriented, cross-platform file paths
Join with /, and get .exists(), .name, .suffix, .glob() - the modern way to handle paths.
os / os.path
Older procedural path and folder tools
os.path.join, os.listdir - still everywhere; does the same job as pathlib in a different style.
Workshop - a read-transform-write file tool
You will write a small script that reads a text file of rooms, applies a rule, and writes a filtered result to a new file - the input-process-output pattern in miniature.
Python 3 and any text editor. No libraries required (pathlib and os are standard library).
Goal: read a file, transform it, write a new file Inputs: a plain-text list you create (name and a number per line) Time: ~35 minutes
- 1In a text editor, create rooms.txt with a few lines like 'living 18.0', one room per line, and save it next to your script.
- 2Open it with a with block and loop over the lines; use line.split() to separate the name and the number, and float() to turn the number into a value you can compare.
- 3Keep only rooms above a threshold you choose (say 14.0) by collecting them in a list; remember to strip() the newline off each line.
- 4Open a new file large_rooms.txt in write mode and write the kept rooms, adding your own newline after each - then open it in your editor to confirm.
- 5Change the write to append mode and run again; observe that the file grows instead of being overwritten, and note in a comment when each mode is the right choice.
You’ll walk away with
Two files - your input rooms.txt and the script that produces large_rooms.txt - plus a one-line note explaining the difference between 'w' and 'a' mode and why you used with.
Three altitudes on the same idea
Read the band that fits you — or all three.
File handling is where a script starts touching your actual project data. Reading an exported room or door list, filtering it by a rule, and writing back a clean schedule or a checking report is a daily win - and it is the exact foundation the pandas and BIM-extraction lessons build on. Learn to point scripts safely at your project folders with pathlib and you can automate the paperwork that surrounds every drawing set.
Your schedules, specs and lists all live in files - now your scripts can read and write them. Pull a finishes list out of a text or CSV export, apply an allowance, and write a tidy version back; append new items to a running spec without retyping the rest. This lesson is the plumbing behind every 'get the data in, get a clean version out' task that eats your evenings.
Reading and writing files is the skill that makes assignments feel real. Instead of hard-coding data into your script, you load it from a file and write results out - exactly how professional tools work. Master with open(), the read and write modes, and pathlib now; the data-heavy modules ahead assume you can get information in and out of files without thinking about it.
“You have to remember to close every file you open, and forgetting will corrupt your data.”
Do it yourself
Think it through, then confirm by running.
- 1Why is opening a file inside a with block better than calling open() and close() yourself?
- 2What is the difference between 'w' mode and 'a' mode, and which one can destroy data?
- 3What does line.strip() remove, and why do you usually need it when reading lines?
- 4Does f.write() add a newline for you? If not, how do you get one?
- 5What is the difference between a relative and an absolute path, and why prefer pathlib to gluing strings?
The one line to carry out
Peer-reviewed journals & authoritative standards
- 01The Python Tutorial - Reading and Writing Files — Python Software Foundation, 2026.
- 02os - Miscellaneous operating system interfaces — Python Software Foundation, 2026.
- 03The Python Standard Library — Python Software Foundation, 2026.
- 04String (computer science) — Wikipedia, 2026.
You can now move plain text in and out of files. Real design data, though, has structure - rows and columns, nested settings. Next we read and write it properly with the csv and json modules.
The author
Amogh N P
Architect, interior designer, and creative polymath. Studio Matrx began in his notebooks — his vision of design made honest, useful, and open to everyone. Its Academy is written and taught in his memory, and free, forever.
More about Amogh →