File I/O Basics

Difficulty: Intermediate

File Input/Output (I/O) is one of the most fundamental operations in any programming language. In Python, the built-in open() function is the primary gateway to reading from and writing to files. It returns a file object that provides methods like read(), readline(), write(), and close(). Understanding how file I/O works is essential for data processing, configuration management, logging, and virtually every real-world application.

The open() function accepts two primary arguments: the file path and the mode. The most common modes are 'r' (read, the default), 'w' (write, which truncates existing content), 'a' (append, which adds to the end), 'x' (exclusive creation, fails if file exists), and 'b' (binary mode, used with other modes like 'rb' or 'wb'). You can also combine modes: 'r+' opens for both reading and writing. Choosing the correct mode is critical because 'w' mode will destroy existing file contents without warning.

Reading files can be done in several ways. The read() method reads the entire file into a single string. The readline() method reads one line at a time, which is memory-efficient for large files. You can also iterate over a file object directly in a for loop, which reads line by line. Each approach has its use case: read() is convenient for small files, while line-by-line iteration is essential when processing files that may not fit in memory.

Writing to files uses the write() method for strings or writelines() for a list of strings. The write() method does not automatically add newline characters, so you must include '\n' explicitly. When using 'w' mode, the file is created if it does not exist, or truncated to zero length if it does. The 'a' mode preserves existing content and appends new data at the end.

The with statement (context manager) is the Pythonic way to handle files. It automatically closes the file when the block exits, even if an exception occurs. Without with, you must remember to call close() manually, and forgetting to do so can lead to data loss (buffered writes may not be flushed) or resource leaks (too many open file handles). Always use with when working with files in Python.

Code examples

Reading a File with io.StringIO

import io

# Simulate a file with io.StringIO
f = io.StringIO("Hello, World!\nPython is great.\nFile I/O is easy.")

# read() returns the entire content as a string
content = f.read()
print(content)
print("---")

# Reset position to beginning
f.seek(0)

# readline() reads one line at a time
first_line = f.readline()
print(first_line.strip())

io.StringIO creates an in-memory file-like object. It supports the same methods as a real file: read(), readline(), seek(), write(), etc. We use seek(0) to reset the read position back to the beginning after reading all content.

Writing to a StringIO Object

import io

# Create an empty StringIO object (like opening a file in write mode)
f = io.StringIO()

# Write data
f.write("Line 1\n")
f.write("Line 2\n")
f.write("Line 3\n")

# Get the entire content
print(f.getvalue())

StringIO's write() method works exactly like file write(). The getvalue() method returns all content written so far as a string. This is equivalent to closing a file and reading it back.

The with Statement (Context Manager)

import io

# Using with ensures the file-like object is properly managed
with io.StringIO("apple\nbanana\ncherry") as f:
    for line in f:
        print(line.strip())

# In real Python, you would write:
# with open('fruits.txt', 'r') as f:
#     for line in f:
#         print(line.strip())
print("File closed:", f.closed)

The with statement automatically calls close() when the block ends. Iterating over a file object reads it line by line, which is memory efficient. Each line includes a trailing newline, so strip() removes it.

File Open Modes Explained

import io

# 'w' mode: write (truncates existing content)
f = io.StringIO("old content")
f.seek(0)
f.write("new content")
f.truncate()  # Remove anything after current position
print("Write mode:", f.getvalue())

# 'a' mode: append (adds to end)
f2 = io.StringIO("existing data\n")
f2.seek(0, 2)  # Seek to end (like append mode)
f2.write("appended data\n")
print("Append mode:", f2.getvalue())

In write mode ('w'), existing content is replaced. In append mode ('a'), new content is added after existing data. With StringIO, we simulate these modes using seek() and truncate().

Key points

Concepts covered

open(), Read Modes, Write Modes, with Statement, io.StringIO