Studio Matrx Monthly · Volume 1 · Issue 3 · August 2026
Amogh N P
 In loving memory of Amogh N P — Architect · Designer · Visionary 
Reading & Writing FilesLesson 3.3
PSD for Architecture, Planning & Urban Design/Module 3 · Functions, Modules & Files

Lesson 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

13 min Interactive lessonFree · open lessonByAmogh N P· Architect & interior designer
The hook

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:

python
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.

open() ... WITH ... CLOSEopen(path)choose a moderead / writeinside the blockclosewith does it for youwith open("rooms.txt") as f: text = f.read() # then the file auto-closes here
Zoom
The with block frames every file operation: open() the file choosing a mode, read or write inside the indented block, and the context manager closes it for you at the end - even if an error is raised. This is why you always open files with 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:

python
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 newline

read() 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:

python
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 lost

Two 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.

PATHS & FOLDERSproject/|-- data/| |-- rooms.csv| |-- finishes.json|-- exports/|-- schedule.pypathlib builds paths safelyfrom pathlib import Pathp = Path("data") / "rooms.csv"The / operator joins parts with theright separator on any operating system.Relative paths start from where you run the script; absolute paths start from the disk root.
Zoom
A real project is folders, so scripts need paths. pathlib treats a path as an object you build with the / operator - Path('data') / 'rooms.csv' - and joins the parts with the right separator on any operating system, instead of fragile string concatenation.

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:

python
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 folder

The 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:

python
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.

Terms & tools you'll meet in this lesson

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.

Hands-on workshop

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).

Given & goal
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
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

The worked example

Three altitudes on the same idea

Read the band that fits you — or all three.

For the architectAutomate busywork & build custom tools

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.

For the interior designerScripts for data, schedules & layouts

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.

For the studentA hireable computational skill

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.

Misconception check

You have to remember to close every file you open, and forgetting will corrupt your data.

The worry is real but the solution is simple, and it is not manual discipline. Yes, files left open can lose buffered data or lock resources - but you almost never call close() yourself in modern Python. The with statement (a context manager) closes the file for you automatically at the end of its block, even if an error is raised partway through. So the correct habit is not 'remember to close every file' - it is 'always open files inside a with block' and then never think about closing at all. You will occasionally see older code using open() and close() as separate calls; treat with as the default and the whole class of forgot-to-close bugs simply disappears.
Try it

Do it yourself

Think it through, then confirm by running.

  1. 1Why is opening a file inside a with block better than calling open() and close() yourself?
  2. 2What is the difference between 'w' mode and 'a' mode, and which one can destroy data?
  3. 3What does line.strip() remove, and why do you usually need it when reading lines?
  4. 4Does f.write() add a newline for you? If not, how do you get one?
  5. 5What is the difference between a relative and an absolute path, and why prefer pathlib to gluing strings?
Take this with you

The one line to carry out

Open files with with open(path, mode) as f, read with read/readlines/looping, write with write (adding your own newline), and describe locations with pathlib - and your scripts finally read and write real project data. 'w' overwrites, 'a' appends: choose deliberately.
Take it further
References & further reading

Peer-reviewed journals & authoritative standards

  1. 01The Python Tutorial - Reading and Writing FilesPython Software Foundation, 2026.
  2. 02os - Miscellaneous operating system interfacesPython Software Foundation, 2026.
  3. 03The Python Standard LibraryPython Software Foundation, 2026.
  4. 04String (computer science)Wikipedia, 2026.
Related lessons
Recap
Files are how scripts touch real data. open() gives a file object and the with block closes it safely for you, so always open inside with. Read a whole file with read(), a list of lines with readlines(), or loop line by line for large files - stripping the trailing newline as you go. Writing needs a mode: 'w' overwrites, 'a' appends, and write() does not add newlines for you. Describe file locations with paths, using pathlib's / to join parts safely rather than string concatenation.
Carry forward →

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.

A

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 →