Python Write & Create Files
Master techniques for creating files, appending data, managing encodings, and persisting structured information.
Opening Files for Writing
Use 'w', 'a', or 'x' modes depending on your goal:
'w': Overwrite existing file or create a new one.'a': Append to the end; creates the file if it does not exist.'x': Create new file and raise an error if it already exists.
with open("notes.txt", "w", encoding="utf-8") as file:
file.write("Project ideas\n")Opening in text mode replaces newline characters with the system-specific sequence when writing.
Writing Strings
file.write() stores a string and returns the number of characters written. Remember to include newline characters manually.
tasks = ["Refactor", "Review", "Deploy"]
with open("tasks.txt", "w", encoding="utf-8") as file:
for task in tasks:
file.write(f"{task}\n")Writing Multiple Lines
Use file.writelines(iterable) to write several strings without automatically adding newline characters.
lines = ["First line\n", "Second line\n", "Third line\n"]
with open("output.txt", "w", encoding="utf-8") as file:
file.writelines(lines)Ensure each element already contains newline characters if you want line breaks.
Append Mode
Preserve existing content and add new data to the end of the file with append mode.
with open("events.log", "a", encoding="utf-8") as file:
file.write("INFO: Service restarted\n")Append mode moves the file pointer to the end automatically, so no seek() call is required.
Binary Mode Writing
Write binary data (images, audio, serialized bytes) with 'wb' or 'ab'. Data must be bytes-like.
data = bytes([0x89, 0x50, 0x4E, 0x47])
with open("header.bin", "wb") as file:
file.write(data)When converting strings to bytes, specify the encoding: text.encode('utf-8').
Structured Data Formats
Leverage dedicated libraries to serialize dictionaries, lists, and other objects.
json.dump(obj, file, indent=2)for JSON.csv.writer(file)orcsv.DictWriterfor CSV.pickle.dump(obj, file)for Python-specific binary serialization (trusted input only).
import json
payload = {"email": "info.studygrid@gmail.com", "active": True}
with open("user.json", "w", encoding="utf-8") as file:
json.dump(payload, file, indent=2)File Creation Safeguards
Use the exclusive creation mode to avoid overwriting important data.
try:
with open("archive.zip", "x") as file:
file.write("binary data")
except FileExistsError:
print("Archive already exists")Alternatively, check with Path.exists() before writing.
Flushing and Buffering
Python buffers writes for performance. Call file.flush() to force writing buffered data to disk, or open the file with buffering=1 for line buffering.
with open("live.log", "a", encoding="utf-8") as file:
file.write("User signed in\n")
file.flush()Error Handling
Intercept PermissionError, FileExistsError, or OSError to deliver actionable feedback.
from pathlib import Path
path = Path("/restricted/log.txt")
try:
with path.open("w", encoding="utf-8") as file:
file.write("Audit trail\n")
except PermissionError:
print("Cannot write to the target directory.")Best Practices
- Specify
encoding="utf-8"for text output. - Write to temporary files first and atomically replace the original to avoid corruption.
- Lock critical files when writing from multiple processes (use
filelockor platform APIs). - Validate user-supplied filenames to prevent directory traversal.
Next Steps
Learn how to remove obsolete files, clear directories, and guard against accidental deletions in the next chapter.
write vs writelines, and Newlines
write() does not add a newline — you must include \n yourself. writelines() writes a list of strings (also without adding newlines).
lines = ["apple\n", "banana\n", "cherry\n"]
with open("fruits.txt", "w", encoding="utf-8") as f:
f.writelines(lines)
# print() to a file adds newlines for you
with open("fruits.txt", "a", encoding="utf-8") as f:
print("date", file=f)Write vs Append — Don't Lose Data
Opening in "w" mode truncates the file to empty the instant you open it — before you write anything. If you meant to add to an existing file, use "a".
with open("log.txt", "a", encoding="utf-8") as f: # append, keep history
f.write("2026-08-18: started\n")Try It Yourself
Exercise 1: Write the numbers 1–5, each on its own line.
Show solution
with open("nums.txt", "w", encoding="utf-8") as f:
for n in range(1, 6):
f.write(f"{n}\n")Exercise 2: Save a list of names to a file using writelines.
Show solution
names = ["Ann", "Bob", "Cara"]
with open("names.txt", "w", encoding="utf-8") as f:
f.writelines(name + "\n" for name in names)Key Takeaways
write()needs explicit\n;print(..., file=f)adds it."w"truncates;"a"appends.- Use
withandencoding="utf-8".
📘 Real-World Deep Dive
Writing files well is the other half of the data-pipeline story. Knowing the difference between text/binary, atomic writes, and buffered output protects you from truncated-output bugs that don't show up in dev.
Real-Life Scenario
Atomic, deduplicated writes: maintain a "current" file and rotate to a timestamped backup each run, all without ever leaving a half-written file on disk.
Real-Life Example
import os, tempfile, time
from pathlib import Path
def atomic_write(path: Path, content: str) -> None:
"""Write to a temp file in the same dir, then rename — atomic on POSIX & NTFS."""
tmp_dir = path.parent
tmp_dir.mkdir(parents=True, exist_ok=True)
fd, tmp_name = tempfile.mkstemp(prefix=path.name + ".", dir=tmp_dir)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.write(content)
os.replace(tmp_name, path)
except Exception:
if os.path.exists(tmp_name):
os.unlink(tmp_name)
raise
def rotate(path: Path) -> None:
if path.exists():
backup = path.with_suffix(path.suffix + "." + time.strftime("%Y%m%d-%H%M%S"))
path.rename(backup)
print(f"rotated -> {backup.name}")
atomic_write(path, "# new run\n")
print(f"wrote -> {path.name} ({path.stat().st_size} bytes)")
rotate(Path("report.out"))Expected Output
rotated -> report.out.20260820-214830
wrote -> report.out (10 bytes)Common mistakes
- A
write()interrupted by a crash leaves a half-written file — use atomic rename. - Forgetting
flush/closeon buffered writes hides crashes until the next read fails. - Opening a file in
"w"truncates it before the first byte — guard for "append" vs. "overwrite" intent.
🚀 Performance & Best Practices
- Buffer with
io.open(..., buffering=64*1024)if you write many small pieces. - For binary data, use
"wb"withstruct.packinstead of building strings first. - On Linux,
O_DIRECTbypasses the page cache for huge files — useful for ETL pipelines.
🧪 Try It Yourself
- Add zstandard compression when the content is bigger than 1 MB.
- Wrap
atomic_writein a context manager that retries on lock contention. - Profile the rotation against simply
open(path, "w")on a 100 MB file.