File Handling & Context Managers
File Handling & Context Managers. Read and write files, handle binary streams, manage file pointers, and write custom context managers using the with block.
Downloaded from PrepIQ (https://prepiq.online)
Q1. What are the different file open modes in Python?
| Mode | Meaning |
|---|---|
| 'r' | Read (default) - error if file doesn't exist |
| 'w' | Write - creates file, TRUNCATES (erases) existing content |
| 'a' | Append - creates file if missing, writes at the end |
| 'x' | Exclusive creation - errors if file already exists |
| 'b' | Binary mode suffix (e.g. 'rb', 'wb') |
| '+' | Read and write suffix (e.g. 'r+', 'w+') |
with open('data.txt', 'r') as f:
content = f.read()
with open('data.txt', 'w') as f:
f.write('New content') # WARNING: erases everything that was there before
with open('data.txt', 'a') as f:
f.write('\nAppended line') # adds to the end, keeps existing content
with open('image.png', 'rb') as f:
binary_data = f.read() # bytes, not str
with open('new_file.txt', 'x') as f:
f.write('Created fresh') # FileExistsError if new_file.txt already exists
Q2. Why should you use the 'with' statement when working with files?
The 'with' statement guarantees the file is closed automatically, even if an exception occurs, preventing resource leaks and file corruption from unclosed handles.
# WITHOUT 'with' - manual close required, easy to forget on exceptions
f = open('data.txt', 'r')
data = f.read()
result = 10 / 0 # exception occurs here
f.close() # NEVER REACHED - file stays open, resource leak
# WITH 'with' - close is guaranteed automatically
with open('data.txt', 'r') as f:
data = f.read()
result = 10 / 0 # exception occurs
# f.close() is STILL called automatically as the exception propagates
print(f.closed) # True - confirmed closed even though the exception interrupted execution
# Multiple files in one 'with' statement
with open('input.txt') as infile, open('output.txt', 'w') as outfile:
outfile.write(infile.read().upper())
# both files closed automatically
# Why it matters at scale: too many open file handles can hit OS limits
for i in range(10000):
with open(f'file_{i}.txt', 'w') as f: # each one properly closed
f.write('data')
# vs forgetting to close in a loop - could exhaust file descriptor limits
Q3. What is the difference between read(), readline(), and readlines()?
| Method | Returns | Memory usage |
|---|---|---|
| read() | Entire file content as one string | High for large files - loads everything |
| readline() | One line at a time (as a string) | Low - reads incrementally |
| readlines() | List of ALL lines as strings | High - loads everything into a list |
# read() - entire file as one string
with open('data.txt') as f:
content = f.read()
print(type(content)) # <class 'str'>
# readline() - one line at a time, good for large files
with open('data.txt') as f:
line1 = f.readline()
line2 = f.readline()
print(line1, line2)
# readlines() - list of all lines
with open('data.txt') as f:
lines = f.readlines()
print(type(lines)) # <class 'list'>
for line in lines:
print(line.strip()) # strip() removes trailing \n
# MOST MEMORY-EFFICIENT: iterate the file object directly (like readline in a loop)
with open('large_file.txt') as f:
for line in f: # reads one line at a time, never loads the whole file
process(line.strip())
# This is the recommended approach for large files instead of readlines()
Q4. How do you read and write JSON and CSV files in Python?
# JSON - using the built-in json module
import json
data = {'name': 'John', 'age': 30, 'active': True}
# Writing JSON to a file
with open('user.json', 'w') as f:
json.dump(data, f, indent=2) # indent makes it human-readable
# Reading JSON from a file
with open('user.json', 'r') as f:
loaded = json.load(f)
print(loaded['name']) # John
# Converting between JSON string and Python object without a file
json_string = json.dumps(data) # dict -> JSON string
parsed = json.loads(json_string) # JSON string -> dict
# CSV - using the built-in csv module
import csv
# Writing CSV
with open('users.csv', 'w', newline='') as f: # newline='' avoids extra blank rows
writer = csv.writer(f)
writer.writerow(['name', 'age']) # header
writer.writerow(['John', 30])
writer.writerow(['Jane', 25])
# Reading CSV
with open('users.csv', 'r') as f:
reader = csv.reader(f)
header = next(reader) # skip header row
for row in reader:
print(row) # ['John', '30']
# DictReader/DictWriter - work with rows as dictionaries (uses header automatically)
with open('users.csv', 'r') as f:
dict_reader = csv.DictReader(f)
for row in dict_reader:
print(row['name'], row['age']) # John 30
Q5. How do you write a custom context manager using __enter__ and __exit__?
A class becomes usable with 'with' by implementing __enter__ (setup, returns the value bound to 'as') and __exit__ (cleanup, receives exception details if one occurred).
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
print(f'Opening {self.filename}')
self.file = open(self.filename, self.mode)
return self.file # this becomes the 'as' variable
def __exit__(self, exc_type, exc_value, traceback):
print(f'Closing {self.filename}')
if self.file:
self.file.close()
if exc_type is not None:
print(f'An exception occurred: {exc_value}')
return False # False/None means exceptions propagate normally
with FileManager('test.txt', 'w') as f:
f.write('Hello World')
# Opening test.txt
# Closing test.txt
# Timing context manager - practical example
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_value, traceback):
elapsed = time.time() - self.start
print(f'Elapsed: {elapsed:.4f}s')
with Timer():
total = sum(range(1000000))
# Elapsed: 0.0234s (example)
# Database connection pattern - very common real-world use
class DatabaseConnection:
def __enter__(self):
print('Connecting to database')
self.conn = 'connection_object'
return self.conn
def __exit__(self, exc_type, exc_value, traceback):
print('Closing database connection')
# cleanup happens regardless of success or failure
Q6. How does the contextlib module simplify writing context managers?
The contextlib.contextmanager decorator lets you write a context manager using a generator function instead of a full class with __enter__/__exit__, using yield to separate setup from teardown.
from contextlib import contextmanager
@contextmanager
def managed_file(filename, mode):
print('Setup: opening file')
f = open(filename, mode)
try:
yield f # everything before yield = __enter__, after = __exit__
finally:
print('Teardown: closing file')
f.close() # runs even if an exception occurs in the with block
with managed_file('data.txt', 'w') as f:
f.write('Hello')
# Setup: opening file
# Teardown: closing file
# Comparing class-based vs generator-based approaches
# Class-based (more code, more control)
class Timer:
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, *args):
print(f'{time.time() - self.start:.4f}s')
# Generator-based equivalent (less code, same result)
import time
@contextmanager
def timer():
start = time.time()
yield
print(f'{time.time() - start:.4f}s')
with timer():
sum(range(1000000))
# contextlib.suppress - shorthand for ignoring specific exceptions
from contextlib import suppress
with suppress(FileNotFoundError):
open('might_not_exist.txt') # no error even if file doesn't exist
# Equivalent to:
try:
open('might_not_exist.txt')
except FileNotFoundError:
pass
Q7. How do you handle file paths in a cross-platform way using pathlib?
pathlib.Path provides an object-oriented, cross-platform way to work with file paths, replacing manual string concatenation and the older os.path module for most use cases.
from pathlib import Path
# Creating paths - works correctly on Windows, Linux, macOS
p = Path('data') / 'files' / 'report.txt'
print(p) # data/files/report.txt (or data\files\report.txt on Windows)
# Compare to error-prone manual string concatenation
# bad_path = 'data' + '/' + 'files' + '/' + 'report.txt' # breaks on Windows
# Common Path operations
p = Path('data/report.txt')
print(p.name) # report.txt
print(p.stem) # report (without extension)
print(p.suffix) # .txt
print(p.parent) # data
print(p.exists()) # True/False
print(p.is_file()) # True/False
print(p.absolute()) # full absolute path
# Reading/writing directly without open()
content = p.read_text() # reads entire file as string
p.write_text('New content') # writes string to file
# Creating directories
Path('new_folder').mkdir(exist_ok=True) # exist_ok avoids error if it already exists
Path('nested/folders/here').mkdir(parents=True, exist_ok=True) # creates all levels
# Iterating files in a directory
for file in Path('.').glob('*.txt'): # all .txt files in current dir
print(file)
for file in Path('.').rglob('*.py'): # recursive - all .py files in subdirectories too
print(file)
# Combining with open() when needed for more control
with open(p, 'r') as f:
data = f.read()
Q8. What is the difference between text mode and binary mode when working with files?
| Mode | Data returned | Handles |
|---|---|---|
| Text mode ('r', 'w') | str | Automatic encoding/decoding, newline translation |
| Binary mode ('rb', 'wb') | bytes | Raw bytes, no encoding/decoding or newline translation |
# Text mode - automatically decodes bytes to str using an encoding
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(type(content)) # <class 'str'>
# Binary mode - raw bytes, no decoding
with open('image.png', 'rb') as f:
content = f.read()
print(type(content)) # <class 'bytes'>
# Attempting text mode on a binary file often raises UnicodeDecodeError
# with open('image.png', 'r') as f:
# f.read() # UnicodeDecodeError: invalid start byte
# Explicitly specifying encoding avoids platform-dependent default encoding issues
with open('data.txt', 'r', encoding='utf-8') as f: # always specify encoding explicitly
content = f.read()
# Writing bytes
with open('output.bin', 'wb') as f:
f.write(b'\x00\x01\x02') # bytes literal, prefixed with b
# Converting between str and bytes manually
text = 'Hello'
encoded = text.encode('utf-8') # str -> bytes
decoded = encoded.decode('utf-8') # bytes -> str
print(encoded) # b'Hello'
print(decoded) # Hello
Q9. How do you handle large files efficiently in Python without running out of memory?
The key principle is to avoid loading the entire file into memory at once - process it in chunks or line by line using generators and iteration instead.
# BAD - loads the entire file into memory at once
with open('huge_file.txt') as f:
lines = f.readlines() # could use gigabytes of RAM for a large file
for line in lines:
process(line)
# GOOD - iterating the file object reads one line at a time
with open('huge_file.txt') as f:
for line in f: # memory-efficient, lazy line-by-line reading
process(line.strip())
# Reading in fixed-size CHUNKS for binary or very long-line files
def read_in_chunks(file_path, chunk_size=1024 * 1024): # 1MB chunks
with open(file_path, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
yield chunk
for chunk in read_in_chunks('large_video.mp4'):
process_chunk(chunk)
# Using a generator to process a large CSV without loading it all at once
import csv
def process_large_csv(filepath):
with open(filepath) as f:
reader = csv.DictReader(f)
for row in reader: # each row processed and discarded, not accumulated
yield transform(row)
for result in process_large_csv('massive_data.csv'):
save_to_database(result)
# For structured data too big for memory, consider pandas with chunksize
import pandas as pd
for chunk_df in pd.read_csv('huge.csv', chunksize=10000):
process_dataframe_chunk(chunk_df)
Q10. What happens if you don't close a file properly, and how do you check if a file is closed?
Failing to close files can lead to resource leaks, data not being flushed to disk (partial writes), and eventually hitting OS limits on open file handles for long-running programs.
# Checking if a file is closed
f = open('data.txt', 'w')
print(f.closed) # False - still open
f.close()
print(f.closed) # True
# Writing without closing may not flush data to disk immediately
f = open('data.txt', 'w')
f.write('Important data')
# If the program crashes HERE, the write may never reach disk -
# file buffers are often only flushed on close() or explicit flush()
f.close() # now guaranteed to be written
# Manually flushing without closing (useful for logs written over time)
f = open('log.txt', 'w')
f.write('Log entry 1\n')
f.flush() # forces the OS to write buffered data now, file stays open
f.write('Log entry 2\n')
f.close()
# Resource leak example - many open handles without closing
files = []
for i in range(10000):
files.append(open(f'temp_{i}.txt', 'w')) # never closed
# Eventually raises OSError: [Errno 24] Too many open files
# The 'with' statement avoids ALL of these problems automatically
with open('data.txt', 'w') as f:
f.write('Safe data')
# guaranteed closed and flushed here, even on exceptions
File Handling & Context Managers
File Handling & Context Managers. Read and write files, handle binary streams, manage file pointers, and write custom context managers using the with block.
What are the different file open modes in Python?
ModeMeaning'r'Read (default) - error if file doesn't exist'w'Write - creates file, TRUNCATES (erases) existing...
Why should you use the 'with' statement when working with files?
The 'with' statement guarantees the file is closed automatically, even if an exception occurs, preventing reso...
What is the difference between read(), readline(), and readlines()?
MethodReturnsMemory usageread()Entire file content as one stringHigh for large files - loads everythingreadlin...
How do you read and write JSON and CSV files in Python?
# JSON - using the built-in json module import json data = {'name': 'John', 'age': 30, 'active': True} # Wri...
How do you write a custom context manager using __enter__ and __exit__?
A class becomes usable with 'with' by implementing __enter__ (setup, returns the value bound to 'as') and __ex...
How does the contextlib module simplify writing context managers?
The contextlib.contextmanager decorator lets you write a context manager using a generator function instead of...
How do you handle file paths in a cross-platform way using pathlib?
pathlib.Path provides an object-oriented, cross-platform way to work with file paths, replacing manual string...
What is the difference between text mode and binary mode when working with files?
ModeData returnedHandlesText mode ('r', 'w')strAutomatic encoding/decoding, newline translationBinary mode ('r...
How do you handle large files efficiently in Python without running out of memory?
The key principle is to avoid loading the entire file into memory at once - process it in chunks or line by li...
What happens if you don't close a file properly, and how do you check if a file is closed?
Failing to close files can lead to resource leaks, data not being flushed to disk (partial writes), and eventu...