Part 5: File Handling & Persistence
Give your Python scripts memory. Learn how to read, write, and append to files so your automation can remember what happened, even after the program ends.
In Part 4, we learned how to break up our code
into reusable pieces with functions and how to defend
it with try-except blocks. But all that logic still
vanished the moment our script stopped running.
That changes now. In this part, your Python code will start to "remember", not just react. You'll learn how to:
-
Read from and write to files using
open(),read(),readline(),readlines(),write(), andclose() -
Understand the difference between
'r','w', and'a'modes - Avoid common bugs like forgetting to close files or accidentally overwriting data
- Use
with open()to safely manage file access
This is a major leap in automation power - giving your scripts long-term memory so they can log activity, store inputs, and resume where they left off. Whether you're building an agent-assist tool, keeping logs of escalations, or storing intermediate outputs, this is how your code graduates from temporary to persistent.
-
Click the menu icon (top-left of the editor below) to:
- Reset to start over with fresh code
- Download your script to reuse it later
- Go fullscreen if you want more room to code
Intro & Recap
You’ve already built scripts that route calls using lists, store customer profiles in dictionaries, lock in metadata with tuples, and build resilient, modular logic using functions and error handling.
But there’s one thing your code still can’t do: remember. As soon as the program ends, all your carefully stored data, customer IDs, ticket reasons, even escalation logs, vanishes into thin air.
In the real world, that's a deal-breaker. Contact centers, bots, and CX tools need a memory. They must be able to log activity, retrieve past inputs, and store user history across sessions.
That’s where persistence comes in, the ability to save and load data from files.
This part teaches you how to:
-
Read from and write to files using
open(),read(), andwrite() -
Safely manage file operations using
with open(), a Python best practice - Store logs, notes, and data across sessions using plain text files
- Work with structured formats like JSON and CSV, the building blocks of modern automation and data exchange
Once you know how to work with files, you're no longer building one-off scripts. You're building systems that remember, improve, and integrate. Let’s get into it.
What is File I/O in Python?
File I/O stands for Input and Output, reading from and writing to files. In the CX world, this might mean:
- Reading an escalation log to understand what happened last shift
- Writing a customer note during a live chat for later handoff
- Appending a new ticket to a system log — without overwriting the old ones
In Python, this is done using the built-in
open() function, like so:
file = open("notes.txt", "r") # Open for reading
contents = file.read()
print(contents)
file.close()
Notice the flow: you open() the file, do your
read() or write() operations, and then
explicitly close the file.
close() the file, you could lose unsaved data,
leave system resources locked, or cause strange bugs, especially
if multiple users or processes are involved.
Here’s a quick guide to the most common modes you’ll use with
open():
| Mode | What It Does |
|---|---|
"r" |
Read (default mode). File must exist. |
"w" |
Write. Overwrites the file if it exists. |
"a" |
Append. Adds to the end of the file if it exists. |
"x" |
Create a new file. Fails if file already exists. |
"b" |
Binary mode (e.g. "rb" for reading
images/files)
|
"w" and "b" to write binary data, or
"a+" to append and read.
Let us now go ahead and use this knowledge to build a simple log
viewer. Later, we’ll show a safer, more modern way to handle
files with
with open(), but this classic pattern is important
to understand first.
Reading from a File
Let’s say you want to review escalation logs from earlier this week. If your Python script can read text files, it can summarize escalations, count issue types, or surface recurring customer pain points.
Let’s read from a file called escalations.txt using
the manual approach.
try:
file = open("escalations.txt", "r")
contents = file.read()
print(contents)
file.close()
except FileNotFoundError:
print("Log file not found. Try running the script that creates it first.")
That’s the full process: open the file in read mode
("r"), read it, then remember to
close it when you’re done.
Other Ways to Read
Sometimes you don’t want the whole file at once. You want to process it line-by-line. Here are a few options:
# Read just the first line
file = open("escalations.txt", "r")
print(file.readline())
file.close()
# Read all lines as a list
file = open("escalations.txt", "r")
lines = file.readlines()
for line in lines:
print(line)
file.close()
# Loop through file directly
file = open("escalations.txt", "r")
for line in file:
print(line)
file.close()
\n newline character. Use .strip() to
remove them before displaying or processing:
print(line.strip())
Right now you’re opening and closing files manually. That’s
okay, but fragile. If your script crashes before
file.close(), the file stays open!
That’s why in the next section, we’ll introduce a better, safer
way: with open().
Writing & Appending to a File
Just like reading helps you review old interactions, writing lets you create new records, like logging an escalation, saving a chat transcript, or outputting a customer summary to a file.
Here’s how to write to a new file. If the file exists, it’ll be replaced.
file = open("escalations.txt", "w")
file.write("Customer ID: 2392 | Reason: Escalated due to system outage\n")
file.close()
That wrote one line to the file. If you run it again, it’ll overwrite the previous contents.
"w" mode
erases existing contents. Don’t use it unless
you intend to start fresh.
To append a new line to an existing file
instead, use mode "a":
file = open("escalations.txt", "a")
file.write("Customer ID: 7154 | Reason: Escalated for billing confusion\n")
file.close()
That adds the new line without wiping out what was already there. Very useful for ongoing logs or journals.
Quick Review of Modes
"w"— write (overwrite mode)"a"— append (add to end of file)
Next up, we’ll introduce with open() so you don’t
have to remember to call close() every time.
Using with open() — The Safe Way
Remember how we had to manually call
file.close() every time? That works, but it’s
risky. What if your script crashes before it gets to
close()? The file might stay locked, or worse, your
data might not be saved.
close() or
your script errors out, you risk corrupting files or leaving
them in a half-written state.
That’s why most production Python code uses the
with open() pattern, also called a
context manager. It opens the file, lets you
work inside a block, and
automatically closes it afterward, even if an
error happens.
Rewriting Our Previous Example
Here’s how to write to a file the safer way:
with open("escalations.txt", "a") as file:
file.write("Customer ID: 9135 | Reason: Escalated for tone mismatch\n")
That’s it. The file is closed automatically when the block ends. You don’t have to worry about forgetting anything.
Reading is just as clean:
with open("escalations.txt", "r") as file:
for line in file:
print(line.strip())
with open() when dealing with files. It’s cleaner,
safer, and prevents bugs you’ll regret later.
From this point on, we’ll use
with open() exclusively. It’s how you build
production-grade scripts, especially in day to day automation
workflows where logs and notes matter.
Reading and Writing with JSON
In the real world, your automations won't just deal with plain text. You'll often interact with structured data from APIs, error logs, databases, or platform configs. The most common format? JSON, short for JavaScript Object Notation.
JSON is the standard format for storing structured information
like customer records, entities for bots, or escalation
templates. And Python gives you everything you need to read and
write JSON with the json module.
Let’s say you have a config file like this:
{
"escalation_categories": ["billing", "technical", "complaint"],
"default_priority": "medium",
"max_notes_per_ticket": 3
}
You can load and use this in Python like this:
import json
# Read JSON config from file
with open("config.json", "r") as file:
config = json.load(file)
print(config["escalation_categories"])
print("Default priority:", config["default_priority"])
Writing JSON is just as easy:
# Update something
config["default_priority"] = "high"
# Save it back to file
with open("config.json", "w") as file:
json.dump(config, file, indent=2)
indent=2 to make it human-readable. Without it, the
file becomes one long line.
JSON is your go-to for saving structured settings or customer data in a readable, portable format. You’ll use it all the time when working with webhooks, CPaaS, or bot configurations.
-
Keys must be strings —
{"name": "John"}is valid, but{name: "John"}is not. -
json.load()reads from a file.json.loads()reads from a string. -
json.dump()writes to a file.json.dumps()returns a string.
In our upcoming mini project, we’ll use JSON to simulate a config file that defines escalation priorities and categories. That’s how real systems scale, they read from structured data, not hardcoded lists.
Working with CSV Files
While JSON is great for configs and APIs, a lot of data you’ll work with, especially logs, reports, or exported records, comes in CSV format. CSV stands for Comma-Separated Values, and it's the universal way of storing rows and columns of data in plain text.
For example, an exported escalation log might look like this:
customer_id,reason,agent,timestamp
2345,Complaint escalation,Ash,2024-07-11 09:20
1888,Billing dispute,Lee,2024-07-11 10:15
Python has a built-in csv module that makes it easy
to read and write this format.
Reading a CSV File
import csv
with open("escalation_log.csv", "r") as file:
reader = csv.reader(file)
for row in reader:
print(row)
Each row is a list. If your file has a header row,
you can skip it manually or use csv.DictReader to
treat each row as a dictionary:
with open("escalation_log.csv", "r") as file:
reader = csv.DictReader(file)
for row in reader:
print("Customer:", row["customer_id"], "Agent:", row["agent"])
Writing to a CSV File
# Add a new escalation row
new_row = ["3901", "Product malfunction", "Zara", "2024-07-11 11:45"]
with open("escalation_log.csv", "a", newline="") as file:
writer = csv.writer(file)
writer.writerow(new_row)
newline="" when
opening CSV files to avoid extra blank lines, especially on
Windows.
CSVs are ideal for creating daily reports or integrating with tools that expect spreadsheet-style data.
- Use JSON for nested or structured data (e.g., config files, inputs to webhooks, etc).
- Use CSV for flat, table-style data (e.g., reports, logs, dashboards).
In our mini project, we'll extend the escalation logger to offer optional CSV export, perfect for team reporting and operational dashboards.
Mini Project: Escalation Log with Memory & Export
It’s time to tie it all together, input handling, persistence, dictionaries, JSON, and CSV into one cohesive project. Think of this like a smart agent-assist tool that logs escalations with customer metadata and stores it both for immediate reference and future analysis.
Here's what the program will do:
- Use a fake customer database (dictionary with nested fields).
- Let an agent enter a Customer ID and the reason for escalation.
- Lookup the customer profile and confirm it (demoing tuples, sets, etc.).
- Store the log in a JSON file for structured history.
- Support viewing the JSON escalation history anytime.
- Export the structured data to a CSV file for dashboards.
-
All input is validated with
get_valid_input()and all file operations are robust and wrapped in error handling.
Here's the full script. You can paste and run this in any Python environment.
import json
import csv
import os
import random
def get_valid_input(prompt, expected_type=str):
while True:
try:
value = input(prompt).strip()
if expected_type == int:
return int(value)
elif expected_type == float:
return float(value)
elif expected_type == str:
if value == "":
raise ValueError("Input cannot be empty.")
return value
except ValueError as e:
print(f"Error: {e}. Please try again.")
# Sample database (from Part 3 style)
customer_db = {
"C001": {
"name": "Alice",
"tags": {"VIP", "Singapore"},
"registered": ("2021-05-21", "agentA"),
},
"C002": {
"name": "Bob",
"tags": {"Trial", "Philippines"},
"registered": ("2022-01-10", "agentB"),
},
}
def fetch_customer_profile(customer_id):
profile = customer_db.get(customer_id)
if profile:
print(f"\nCustomer: {profile['name']}")
print(f"Tags: {', '.join(profile['tags'])}")
print(f"Registered: {profile['registered'][0]} (by {profile['registered'][1]})\n")
else:
print("No customer profile found.\n")
return profile
def log_customer_query(customer_id, reason):
log_entry = {
"id": customer_id,
"reason": reason,
"agent": "system_user",
}
try:
# Simulate potential sync failure (API down, etc.)
if random.random() < 0.2:
raise ConnectionError("Remote logging failed. Will retry later.")
print("Query successfully logged to central system.\n")
except Exception as e:
print(f"{e}\n")
log_entry["status"] = "PENDING SYNC"
finally:
print("Log attempt complete.\n")
return log_entry
def save_to_json(data, filename="escalation_log.json"):
try:
if os.path.exists(filename):
with open(filename, "r") as f:
existing = json.load(f)
else:
existing = []
existing.append(data)
with open(filename, "w") as f:
json.dump(existing, f, indent=2)
except Exception as e:
print(f"Error writing to JSON: {e}")
def summarize_queries(filename="escalation_log.json"):
try:
with open(filename, "r") as f:
logs = json.load(f)
print(f"\n--- Escalation Summary ({len(logs)} records) ---")
for entry in logs:
print(f"ID: {entry['id']}, Reason: {entry['reason']}, Status: {entry.get('status', 'OK')}")
print()
except Exception as e:
print(f"Error reading log file: {e}\n")
def export_to_csv(filename_json="escalation_log.json", filename_csv="escalation_export.csv"):
try:
with open(filename_json, "r") as f:
logs = json.load(f)
with open(filename_csv, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["id", "reason", "agent", "status"])
writer.writeheader()
for entry in logs:
writer.writerow(entry)
print(f"CSV export complete: {filename_csv}\n")
except Exception as e:
print(f"Export failed: {e}\n")
def show_menu():
print("1. Log a customer query")
print("2. Review past escalations")
print("3. Export to CSV")
print("4. Exit")
# Main loop
while True:
show_menu()
choice = get_valid_input("Choose an option (1–4): ", expected_type=str)
if choice == "1":
customer_id = get_valid_input("Customer ID: ")
profile = fetch_customer_profile(customer_id)
if profile:
reason = get_valid_input("Escalation reason: ")
log = log_customer_query(customer_id, reason)
save_to_json(log)
elif choice == "2":
summarize_queries()
elif choice == "3":
export_to_csv()
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid option.\n")
customer_db with your own test
users, or hook it up to a CRM API. The fallback
[PENDING SYNC] pattern is useful when working
with intermittent cloud logging.
tags in customer_db. Bonus: color
the output differently if you’re using a rich console library!
Other Patterns You’ll Start Seeing
Now that you’re handling persistent files, especially ones with structured data, you'll notice some new patterns showing up often.
-
strip()– Used to clean extra spaces or newlines from file content.
Why? When you read from a file, every line ends in\n. To avoid messy formatting or logic bugs, clean it first:line = line.strip() -
split()– Used to break strings into parts (words, values, etc.).
Why? If you're parsing plain-text logs, you'll often need to extract data:entry = "Customer ID: 1234 | Reason: Billing issue" parts = entry.split("|") id_part = parts[0].strip() reason_part = parts[1].strip() -
json.load()andjson.dump()– The right way to read and write structured JSON files.
Why? JSON is everywhere — APIs, chatbots, analytics tools. You’ll use this to convert between Python and structured text:with open("data.json", "r") as f: data = json.load(f) # loads JSON into Python list or dict with open("data.json", "w") as f: json.dump(data, f, indent=2) # writes Python data to JSON file -
try/except– More than just input handling.
Why? You'll often wrap file reads in try blocks to gracefully handle missing files or bad formats:try: with open("missing.txt", "r") as file: contents = file.read() except FileNotFoundError: print("File not found. Please check the name.")
Gotchas and How to Avoid Them
Persistence is powerful, but it also opens the door to new problems. Here are common mistakes you’ll run into (and how to sidestep them):
-
Forgetting to close files
This used to be a big issue when usingopen()andclose()manually. If the script crashes or exits early, your file might remain locked or unsaved.
Fix: Always usewith open(), it auto-closes for you. -
Overwriting your data accidentally
Opening a file in'w'mode deletes everything inside it.
Fix: Use'a'(append) to add new lines without erasing the old ones, or confirm with the user before overwriting. -
Assuming files will always exist
If you try to read a file that’s not there, Python throws aFileNotFoundError.
Fix: Usetry/exceptto catch and handle it gracefully. -
Mixing up string and numeric types
Everything read from a file is astring. You can’t do math until you convert it.
Fix: Useint()orfloat()as needed:amount_str = "49.99" amount = float(amount_str) -
Messy formatting in plain-text logs
If you don’t usestrip()andsplit(), you’ll end up with extra spaces, broken logic, or unreadable outputs.
Fix: Clean each line when reading:for line in file: cleaned = line.strip().split("|") -
Corrupting your JSON file
If you write partial or invalid JSON (e.g., during a crash), you might not be able to load it again.
Fix: Alwaysjson.dump()in one go inside awith open()block. Wrap intry/exceptif reliability matters.
A Note Before You Go
You’ve officially crossed the threshold, from learning how to write code, to building scripts that can remember, persist, and interact with the world.
In this part alone, you’ve learned to:
-
Read from and write to files using
open(),read(),write(), and file modes -
Use
with open()to handle files safely and cleanly - Store structured logs in plain text, CSV, and JSON formats
- Build a complete, interactive script that logs and retrieves data like a real CX tool
- Spot and avoid file I/O bugs that could break or corrupt your workflows
The Mini Project you just completed isn’t just an example, it’s a real-world pattern. If you come from the CX world, you've mostly seen these scenarios. I know there are purpose built solutions that can be deployed on call to address these problems, but hey what better way to learn. You’ve built a system that validates input, writes structured data to disk, and retrieves it on demand. That’s not “beginner code.” That’s a pattern you’ll see in real agent-assist systems, escalation trackers, and backend CX tooling.
We are actually wrapping up this series here, I personally think this is enough grounding of basics to get going. There's a part 6 post to this series, but that is more of a post script and shows you the way forward. Till then, happy building!