After learning the basics of text I/O, we must solve two practical problems. The first is how to keep adding new content without erasing the old one, which is appending. The second is how to place and locate files correctly on disk which is path handling. Once you understand these, your program can behave like a reliable journal and a well-organized filing cabinet.
Appending uses the "a" mode. When you open a file with "a", new content is written to the end while preserving everything already there. If the file does not exist, Python creates it first and then appends. The example below writes a timestamped message to the end of app.log. Notice the with open(..., "a", encoding="utf-8") pattern, which automatically closes the file at the end of the block and ensures the data is actually flushed to disk.
Example A:
import time
message = input("Log message: ").strip()
stamp = time.strftime("%Y-%m-%d %H:%M:%S")
with open("app.log", "a", encoding="utf-8") as f:
f.write(f"[{stamp}] {message}n")
print("Appended to app.log")
If you run this multiple times with different messages, app.log grows steadily. To verify the effect, you can immediately read the file back and show only the last few lines as a recap. The snippet below reads the last five lines, or all lines if the file is shorter.
Example B:
with open("app.log", "r", encoding="utf-8") as f:
lines = f.readlines()
print("nLast 5 log entries:")
for line in lines[-5:]:
print(line.strip())
Now let’s move to paths. A path tells your program where to read from and where to write to. A relative path starts from the current working directory, which depends on how and where you launch the program. An absolute path starts from the filesystem root (for example, C:Users... on Windows or /Users/... on macOS/Linux). Many “file not found” errors arise because we assume the relative path is resolved from the script’s folder, while it actually uses the current working directory. To make this explicit, print the working directory first and design your relative paths accordingly.
Example C:
import os
print("Current working directory:", os.getcwd())
If you want your data neatly stored under a specific subfolder, it is good practice to ensure the folder exists before writing. You can call os.makedirs("data", exist_ok=True). After that, write files with a relative path into that folder and keep your project organized.
Example D:
import os
import time
os.makedirs("data", exist_ok=True)
note = input("One line for data/notes.txt: ").strip()
with open("data/notes.txt", "a", encoding="utf-8") as f:
f.write(f"{time.strftime('%H:%M:%S')} {note}n")
print("Written to data/notes.txt")
Manual string concatenation for nested folders is brittle, especially across platforms. Prefer os.path.join or pathlib.Path to build paths. os.path.join chooses the correct separator automatically; pathlib offers an object-oriented API. The example below constructs the same relative location in two ways and writes to it.
Example E:
import os
from pathlib import Path
# Option 1: os.path
folder = os.path.join("data", "logs")
os.makedirs(folder, exist_ok=True)
path1 = os.path.join(folder, "today.log")
# Option 2: pathlib
p = Path("data") / "logs"
p.mkdir(parents=True, exist_ok=True)
path2 = p / "today.log"
with open(path1, "a", encoding="utf-8") as f:
f.write("hello from os.pathn")
with open(path2, "a", encoding="utf-8") as f:
f.write("hello from pathlibn")
print("Wrote via both paths.")
When you switch to absolute paths, remember they tie your code to a specific machine layout. On a different computer, that location may not exist or may require permissions you don’t have. In classroom exercises, prefer relative paths and print the working directory at startup so students see where “relative” actually points. Continue to set encoding="utf-8" for text. If you later write CSV on Windows, you’ll also add newline="" to avoid extra blank lines; that’s a formatting detail for CSV, not required for plain text appends.
A common confusion is using "w" when you meant "a". The "w" mode truncates the file before writing; only "a" preserves old content and writes at the end. To build intuition, try a quick demo: write one line with "w", then write another line with "a", and open the file to observe the difference between overwrite and append.
Hands-on Practice:
data/history/actions.txt. Each run ensures the folder exists, appends a timestamped entry, prints the file’s absolute location, and shows the last three entries. This makes the mapping from relative to absolute locations tangible and demonstrates accumulation over time.Answer example:
import os
from pathlib import Path
import time
base = Path("data") / "history"
base.mkdir(parents=True, exist_ok=True)
logfile = base / "actions.txt"
action = input("What did you just do? ").strip()
stamp = time.strftime("%Y-%m-%d %H:%M:%S")
with open(logfile, "a", encoding="utf-8") as f:
f.write(f"[{stamp}] {action}n")
print("Log file at:", logfile.resolve())
with open(logfile, "r", encoding="utf-8") as f:
last = f.readlines()[-3:]
print("nRecent 3 actions:")
for line in last:
print(line.strip())
If you encounters “file not found” or “permission denied,” the cause is usually path construction or directory permissions. First, print os.getcwd() to confirm the base for relative paths. Then call Path(...).resolve() to see where the file actually ends up. Rename files if the path contains forbidden characters on the platform. For shared or protected directories, you may need help configuring permissions. Treat these as concrete debugging steps that reveal paths are not abstract, they interact with the OS, the working directory, permissions, and character sets.
To wrap up, we revisit the two key ideas: the "a" mode keeps accumulating information, and path resolution determines where the file truly lives. With these in hand, you are ready to save data in more structured formats like CSV and JSON, making your records not only persistent but also easy for other tools and programs to consume. We will learn them in Unit 11: Files & Simple Data Persistence II.