Writing and Creating Files in Python
Learn every way to write files in Python: write(), writelines(), append mode, binary writes, pathlib, encoding, and safe patterns with with blocks.
Writing files is one of the most fundamental I/O operations in Python. Whether you are saving program output, persisting configuration, exporting data to CSV, or logging events, you need a reliable way to create and update files. This chapter covers every approach Python provides: write(), writelines(), append mode, binary writes, newline handling, character encoding, the modern pathlib API, and patterns for writing safely without data loss.
Opening a File for Writing
Every file-writing operation starts with the built-in open() function. The second argument — the mode — controls what happens when you open the file:
| Mode | Meaning | File exists | File missing |
|---|---|---|---|
"w" | Write (text) | Truncates (erases) the file | Creates a new file |
"a" | Append (text) | Moves pointer to end | Creates a new file |
"x" | Exclusive create | Raises FileExistsError | Creates a new file |
"wb" | Write (binary) | Truncates the file | Creates a new file |
"ab" | Append (binary) | Moves pointer to end | Creates a new file |
"r+" | Read + write | Opens in place | Raises FileNotFoundError |
The most important thing to remember about "w" mode: it silently erases the entire file before writing. If you only want to add content to an existing file, use "a" (append) mode instead.
Always supply the encoding parameter when writing text files so your code behaves identically on Windows, macOS, and Linux:
file = open("output.txt", "w", encoding="utf-8")Always Use a with Block
Calling open() without a with block means you must call file.close() yourself. Forgetting to close a file leads to buffered data never being written to disk, too-many-open-files errors in long-running scripts, and file corruption on some operating systems.
The with statement (a context manager) solves all of these problems. Python closes the file automatically when the block exits — even if an exception is raised inside the block.
with open("output.txt", "w", encoding="utf-8") as f:
f.write("Hello, World!\n")
# File is closed and flushed here — guaranteedAll examples in this chapter use the with statement. Avoid the manual open() / close() pattern.
Writing Text with write()
file.write(string) writes the given string to the file and returns the number of characters written. It does not add a newline automatically — you must include \n yourself.
Write a single line to a new file
with open("greeting.txt", "w", encoding="utf-8") as f:
chars_written = f.write("Hello, World!\n")
print(chars_written) # 14Write multiple lines by calling write() repeatedly
with open("poem.txt", "w", encoding="utf-8") as f:
f.write("Roses are red,\n")
f.write("Violets are blue,\n")
f.write("Python is great,\n")
f.write("And so are you.\n")Each call to write() appends to the file at the current position. The file is written fresh (any previous contents are gone) because "w" mode was used.
Writing Multiple Lines with writelines()
file.writelines(iterable) accepts any iterable of strings — a list, a generator, or a tuple — and writes each item in sequence. Like write(), it does not add newlines between items.
Write a list of lines
lines = [
"First line\n",
"Second line\n",
"Third line\n",
]
with open("lines.txt", "w", encoding="utf-8") as f:
f.writelines(lines)If your source data does not already contain \n, add it before writing:
data = ["Alice", "Bob", "Charlie"]
with open("names.txt", "w", encoding="utf-8") as f:
f.writelines(name + "\n" for name in data)The generator expression name + "\n" for name in data is memory-efficient: Python produces each string on demand instead of building the whole list in memory first.
write() vs writelines() — When to Use Each
write() | writelines() | |
|---|---|---|
| Input | A single string | Any iterable of strings |
| Newlines | You control every \n | You control every \n |
| Best for | Building output incrementally | Writing a pre-built sequence at once |
Creating a File That Must Not Already Exist
Use mode "x" (exclusive create) when you want Python to create a new file and fail if the file already exists. This prevents accidentally overwriting important data.
try:
with open("config.txt", "x", encoding="utf-8") as f:
f.write("host=localhost\n")
f.write("port=8080\n")
except FileExistsError:
print("config.txt already exists — not overwriting.")This pattern is useful for generating unique output files (logs, exports, snapshots) where a collision means something went wrong.
Appending to an Existing File
Opening a file with mode "a" moves the write pointer to the end of the file. New content is added after the existing content; nothing is erased.
Append a log entry to an existing file
import datetime
with open("app.log", "a", encoding="utf-8") as f:
timestamp = datetime.datetime.now().isoformat()
f.write(f"[{timestamp}] Server started\n")If app.log does not exist yet, Python creates it. If it does exist, the new line is added at the end. Running the script multiple times builds up a growing log.
Write vs Append — Choosing the Right Mode
- Use
"w"when you want to replace the file's contents entirely (generate a fresh report, save new configuration). - Use
"a"when you want to add to the existing contents (logging, accumulating results over multiple runs).
Newlines and Line Endings
Python's text mode ("w", "a", "r") translates the universal newline \n to the platform-native line ending on write:
- Windows:
\n→\r\n(CRLF) - macOS / Linux:
\nstays\n(LF)
This is usually the right behavior — files written on Windows open correctly in Notepad.
If you need to force a specific line ending regardless of platform — for example, when generating files that must be read by a specific system — pass the newline parameter:
# Force Unix-style LF on all platforms (e.g. for Linux-target files)
with open("unix_file.txt", "w", encoding="utf-8", newline="\n") as f:
f.write("line one\n")
f.write("line two\n")
# Preserve line endings exactly as given (no translation at all)
with open("raw.txt", "w", encoding="utf-8", newline="") as f:
f.write("line one\r\n")
f.write("line two\n")Character Encoding
Always specify encoding= when writing text files. Relying on the platform default risks creating files that cannot be read on other systems.
Recommended encodings for common scenarios:
| Encoding | Use when |
|---|---|
"utf-8" | General purpose; works for all languages; default for most Python projects |
"utf-8-sig" | UTF-8 with BOM — useful for files that will be opened in Excel on Windows |
"latin-1" | Legacy Western-European files |
"cp1252" | Windows ANSI text |
Write a file with UTF-8 encoding
with open("international.txt", "w", encoding="utf-8") as f:
f.write("English: Hello\n")
f.write("Japanese: こんにちは\n")
f.write("Arabic: مرحبا\n")Writing Binary Files
Open a file with mode "wb" (write binary) to write raw bytes instead of strings. Binary mode is required for images, audio, compressed archives, executables, and any non-text data. Do not specify encoding in binary mode.
Write bytes to a binary file
data = bytes([0x89, 0x50, 0x4E, 0x47]) # PNG magic bytes
with open("header.bin", "wb") as f:
f.write(data)
print(f.write(b"\r\n\x1a\n")) # 4Copy a binary file
with open("photo.jpg", "rb") as src:
content = src.read()
with open("photo_backup.jpg", "wb") as dst:
dst.write(content)For large binary files, read and write in chunks to avoid loading the whole file into memory:
CHUNK = 65536 # 64 KB
with open("large.bin", "rb") as src, open("large_copy.bin", "wb") as dst:
while True:
chunk = src.read(CHUNK)
if not chunk:
break
dst.write(chunk)Handling Errors When Writing
A production-quality script always anticipates the ways a file write can fail.
Handle common write errors
try:
with open("/etc/protected.txt", "w", encoding="utf-8") as f:
f.write("data\n")
except PermissionError:
print("Error: you do not have write permission for this file.")
except FileNotFoundError:
print("Error: one or more directories in the path do not exist.")
except IsADirectoryError:
print("Error: the path points to a directory, not a file.")
except OSError as e:
print(f"OS error: {e}")Common exceptions you will encounter:
| Exception | When it occurs |
|---|---|
PermissionError | The process lacks write permission |
FileNotFoundError | An intermediate directory in the path does not exist |
FileExistsError | Mode "x" and the file already exists |
IsADirectoryError | The path points to a directory |
OSError | Disk full, network filesystem error, and other OS-level problems |
See Python Try Except for a full guide to exception handling.
Writing Files Safely (Atomic Write Pattern)
A plain open("file.txt", "w") is not safe for critical data: if your script crashes or is interrupted mid-write, the file is left in a partially-written, corrupted state. The standard solution is an atomic write: write to a temporary file first, then rename it over the target.
import os
import tempfile
def write_file_safely(path, content, encoding="utf-8"):
"""Write content to path atomically using a temp file + rename."""
dir_name = os.path.dirname(os.path.abspath(path)) or "."
# Write to a temp file in the same directory (same filesystem = atomic rename)
fd, tmp_path = tempfile.mkstemp(dir=dir_name)
try:
with os.fdopen(fd, "w", encoding=encoding) as f:
f.write(content)
os.replace(tmp_path, path) # atomic on POSIX; best-effort on Windows
except Exception:
os.unlink(tmp_path) # clean up if something went wrong
raise
write_file_safely("important.txt", "critical data\n")os.replace() (Python 3.3+) replaces the destination atomically on POSIX systems: readers either see the old file or the new file, never a partial write.
Writing Files with pathlib
pathlib.Path (introduced in Python 3.4) provides a concise, object-oriented API. For simple one-shot writes, Path.write_text() and Path.write_bytes() are more readable than open().
Path.write_text()
from pathlib import Path
Path("output.txt").write_text("Hello from pathlib!\n", encoding="utf-8")write_text() opens the file in "w" mode, writes the string, and closes the file — all in one call. It always overwrites the file. There is no append equivalent; for appending use open() with mode "a".
Path.write_bytes()
from pathlib import Path
Path("data.bin").write_bytes(b"\x00\x01\x02\x03")Building Paths with pathlib
pathlib also makes it easy to construct paths safely without string concatenation:
from pathlib import Path
output_dir = Path("results")
output_dir.mkdir(exist_ok=True) # create the directory if needed
report_path = output_dir / "report.txt"
report_path.write_text("Run complete.\n", encoding="utf-8")
print(report_path) # results/report.txt
print(report_path.exists()) # TrueThe / operator on Path objects joins path segments — no need for os.path.join().
Practical Example: Writing a CSV Report
The following complete example writes a list of records to a CSV file using only built-in tools (no csv module), demonstrating several concepts from this chapter together.
from pathlib import Path
import datetime
def write_csv_report(path, headers, rows):
"""Write a simple CSV file with a header row."""
with open(path, "w", encoding="utf-8", newline="") as f:
f.write(",".join(headers) + "\n")
for row in rows:
f.write(",".join(str(v) for v in row) + "\n")
records = [
("Alice", 30, "Engineering"),
("Bob", 25, "Marketing"),
("Charlie", 35, "Finance"),
]
output = Path("staff_report.txt")
write_csv_report(output, ["Name", "Age", "Department"], records)
print(output.read_text(encoding="utf-8"))Expected output:
Name,Age,Department
Alice,30,Engineering
Bob,25,Marketing
Charlie,35,FinanceNote newline="" is passed to open() so that Python does not double-translate line endings inside CSV rows — this matches the recommendation in the Python csv module documentation.
For anything more complex (quoting, dialects, Unicode edge cases) use the built-in Python CSV module instead.
Quick Reference
| Goal | Code pattern |
|---|---|
| Create or overwrite a file | open("f.txt", "w", encoding="utf-8") |
| Append to a file | open("f.txt", "a", encoding="utf-8") |
| Create only if new | open("f.txt", "x", encoding="utf-8") |
| Write binary data | open("f.bin", "wb") |
| Write one string | f.write("text\n") |
| Write a list of strings | f.writelines(lines) |
| One-shot text write | Path("f.txt").write_text("...", encoding="utf-8") |
| One-shot binary write | Path("f.bin").write_bytes(b"...") |
| Safe / atomic write | Write to temp file, then os.replace() |
Related Chapters
- Python File Handling — opening modes,
open()parameters, and thewithstatement - Python Read Files —
read(),readline(),readlines(), line iteration, andseek() - Python Delete Files — deleting files and directories safely
- Python Try Except — handling exceptions in Python
- Python CSV — reading and writing CSV files with the
csvmodule - Python JSON — serializing data to JSON files