W3docs

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:

ModeMeaningFile existsFile missing
"w"Write (text)Truncates (erases) the fileCreates a new file
"a"Append (text)Moves pointer to endCreates a new file
"x"Exclusive createRaises FileExistsErrorCreates a new file
"wb"Write (binary)Truncates the fileCreates a new file
"ab"Append (binary)Moves pointer to endCreates a new file
"r+"Read + writeOpens in placeRaises 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 — guaranteed

All 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)   # 14

Write 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()
InputA single stringAny iterable of strings
NewlinesYou control every \nYou control every \n
Best forBuilding output incrementallyWriting 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: \n stays \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:

EncodingUse 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"))   # 4

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

ExceptionWhen it occurs
PermissionErrorThe process lacks write permission
FileNotFoundErrorAn intermediate directory in the path does not exist
FileExistsErrorMode "x" and the file already exists
IsADirectoryErrorThe path points to a directory
OSErrorDisk 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())  # True

The / 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,Finance

Note 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

GoalCode pattern
Create or overwrite a fileopen("f.txt", "w", encoding="utf-8")
Append to a fileopen("f.txt", "a", encoding="utf-8")
Create only if newopen("f.txt", "x", encoding="utf-8")
Write binary dataopen("f.bin", "wb")
Write one stringf.write("text\n")
Write a list of stringsf.writelines(lines)
One-shot text writePath("f.txt").write_text("...", encoding="utf-8")
One-shot binary writePath("f.bin").write_bytes(b"...")
Safe / atomic writeWrite to temp file, then os.replace()
Was this page helpful?