<< Home

Chapter 10: Files and Input/Output

Programs often need to exchange information with the outside world, such as reading data, saving results, or communicating through text streams. Python’s input and output (I/O) system makes this process simple and expressive. Whether you are reading a configuration file, writing logs, or processing large datasets, Python provides consistent interfaces that work across platforms and file types.

The core concept is the file object. A file object represents an open connection to a file or stream, allowing you to read, write, or both. You usually open a file with the built-in open() function, which returns a handle that you can use within a with statement for automatic cleanup. Once open, you can operate on the file using methods like read(), write(), or readlines().

with open("example.txt", "w", encoding="utf-8") as f:
  f.write("Hello, world!\n")

This pattern ensures the file is properly closed, even if an error occurs. It is part of Python’s design philosophy that explicit resource management should be easy and safe.

Beyond ordinary text files, Python’s I/O system extends to binary data, temporary files, compressed archives, network streams, and in-memory buffers. The standard library offers modules for each of these, from io and pathlib to gzip and zipfile. Working with files becomes a matter of combining these tools cleanly and predictably.

💡 Think of files as streams of bytes. Text files are streams decoded into strings through an encoding such as UTF-8. Being aware of this distinction avoids many common I/O bugs.

In this chapter you will learn how to open, read, and write files safely using with blocks, the differences between text and binary modes, how to work with file paths and directories using pathlib, how to handle exceptions and detect file errors, and how to use modules for structured data such as CSV, JSON, and binary formats

Reading and writing files

File I/O in Python revolves around the built-in open() function. It takes a filename, a mode string that determines how the file will be used, and optional parameters like the character encoding. Once opened, the returned file object provides methods for reading and writing data.

Opening a file

f = open("notes.txt", "r", encoding="utf-8")
content = f.read()
f.close()

Although you can open and close files manually, the preferred and safest pattern is to use a with statement. This automatically closes the file even if an error occurs.

with open("notes.txt", "r", encoding="utf-8") as f:
  content = f.read()
  print(content)
💡 Always use with open(...) when dealing with files. It prevents resource leaks and ensures files are closed cleanly.

Writing text files

To create or overwrite a file, use mode "w". To append to an existing file, use "a". In both cases, open the file in text mode and specify the encoding explicitly.

with open("output.txt", "w", encoding="utf-8") as f:
  f.write("First line\n")
  f.write("Second line\n")

with open("output.txt", "a", encoding="utf-8") as f:
  f.write("Appended line\n")

You can also write multiple lines at once using writelines() if you already have a list of strings.

lines = ["alpha\n", "beta\n", "gamma\n"]
with open("greek.txt", "w", encoding="utf-8") as f:
  f.writelines(lines)

Reading files efficiently

When reading, you can process entire contents at once with read(), a fixed number of bytes with read(n), or line by line with readline() or iteration.

with open("notes.txt", "r", encoding="utf-8") as f:
  for line in f:
    print(line.strip())

Reading line by line is memory efficient and suitable for large files. Each iteration reads only the next line from the stream.

Binary files

When working with non-text data (for example, images, sound files, or compiled programs), open the file in binary mode by adding "b" to the mode string. In this mode, read and write operations deal with bytes objects rather than strings.

# Copy a binary file safely
with open("photo.jpg", "rb") as source, open("backup.jpg", "wb") as target:
  target.write(source.read())
⚠️ In binary mode, no character encoding or newline translation is applied. The data is read and written exactly as it appears on disk.

Modes summary

ModeMeaning
"r"Read text (default)
"w"Write text, truncating the file if it exists
"a"Append text to the end
"rb"Read binary data
"wb"Write binary data
"ab"Append binary data
"r+"Read and write (must already exist)
"w+"Create or overwrite, then read and write

File positions and seeking

You can move the current position in an open file using seek() and find out where you are with tell().

with open("data.bin", "rb") as f:
  f.seek(10)        # skip first 10 bytes
  chunk = f.read(4) # read next 4 bytes
  print(f.tell())   # current position
💡 Seeking works for both text and binary files, though in text mode offsets are measured in decoded characters, not raw bytes. For precise byte offsets, always use binary mode.

These same patterns apply whether you are dealing with small text files or multi-gigabyte binary data: open safely, process in chunks, and close automatically.

Context managers

Context managers make resource handling predictable, elegant, and safe. They are one of Python’s most practical features for writing reliable code that always cleans up after itself.

Python’s with statement is the standard way to manage resources that need to be set up and then cleaned up automatically. It ensures that even if an error occurs inside the block, the resource is released properly. Files are the most common example, but the same pattern works for network connections, database sessions, and locks.

with open("log.txt", "w", encoding="utf-8") as f:
  f.write("Session started\n")
  f.write("All systems nominal\n")

When the with block ends, Python calls the file object’s __exit__() method, which closes the file automatically. This avoids subtle bugs such as leaving files open or forgetting to release system resources.

💡 The with statement is not limited to files. Any object that defines __enter__() and __exit__() methods can be used as a context manager.

How it works

When you write:

with open("example.txt", "r", encoding="utf-8") as f:
  data = f.read()

Python performs the following steps: