Complete guide to Python's os.read function covering low-level file reading, file descriptors, and practical examples.
Last modified April 11, 2025
This comprehensive guide explores Python’s os.read function, which performs low-level file reading using file descriptors. We’ll cover file descriptors, buffer sizes, error handling, and practical examples.
The os.read function reads up to n bytes from a file descriptor. It’s a low-level I/O operation that works directly with file descriptors.
Key parameters: fd (file descriptor), n (maximum bytes to read). Returns bytes read as bytes object. May raise OSError for invalid descriptors.
This basic example demonstrates how to open a file and read its contents using os.read. We first get a file descriptor with os.open.
basic_read.py
import os
fd = os.open(“example.txt”, os.O_RDONLY)
data = os.read(fd, 1024) print(f"Read {len(data)} bytes:") print(data.decode(‘utf-8’))
os.close(fd)
This example opens a file in read-only mode, reads up to 1024 bytes, then closes the descriptor. The data is returned as bytes and needs decoding.
Note that os.read may return fewer bytes than requested, even if more are available. This is normal behavior for low-level I/O operations.
For large files, reading in chunks is more efficient. This example shows how to read a file piece by piece until EOF is reached.
chunked_read.py
import os
CHUNK_SIZE = 4096 # 4KB chunks fd = os.open(“large_file.bin”, os.O_RDONLY)
total_bytes = 0 while True: chunk = os.read(fd, CHUNK_SIZE) if not chunk: # EOF reached break total_bytes += len(chunk) # Process chunk here print(f"Read chunk of {len(chunk)} bytes")
os.close(fd) print(f"Total bytes read: {total_bytes}")
The loop continues reading until os.read returns an empty bytes object, indicating EOF. Each chunk is processed as it’s read.
This approach is memory-efficient as it doesn’t load the entire file at once. The chunk size can be adjusted based on performance needs.
os.read can read from standard input (file descriptor 0). This example demonstrates reading user input directly from stdin.
stdin_read.py
import os import sys
print(“Type something and press Enter (Ctrl+D to end):”)
try: while True: data = os.read(0, 1024) # STDIN_FILENO is 0 if not data: break print(f"You entered: {data.decode(‘utf-8’).strip()}") except KeyboardInterrupt: print("\nInterrupted by user")
This reads directly from standard input until EOF (Ctrl+D) or interrupt. The data is decoded from bytes to a string for display.
Note this is lower-level than input() and requires manual handling of encoding and newlines. It’s useful for binary stdin reading.
With non-blocking file descriptors, os.read can be used for asynchronous I/O. This example shows non-blocking read behavior.
nonblocking_read.py
import os import errno
fd = os.open(“fifo_pipe”, os.O_RDONLY | os.O_NONBLOCK)
try: while True: try: data = os.read(fd, 1024) if data: print(f"Data received: {data.decode()}") else: print(“No data available”) break except BlockingIOError: print(“No data ready - would block”) # In real code, you might wait here break finally: os.close(fd)
When no data is available, non-blocking mode raises BlockingIOError instead of waiting. This is useful for event loops and async I/O.
The example assumes a named pipe (fifo) exists. Real code would need proper error handling and likely an event loop.
os.read is ideal for reading binary files since it returns raw bytes. This example reads a binary file and processes its contents.
binary_read.py
import os
def read_binary_file(filename): fd = os.open(filename, os.O_RDONLY) try: # Read first 4 bytes (could be a magic number) header = os.read(fd, 4) print(f"File header: {header.hex()}")
# Read rest of file
remaining = b''
while True:
chunk = os.read(fd, 4096)
if not chunk:
break
remaining += chunk
return header + remaining
finally:
os.close(fd)
data = read_binary_file(“image.png”) print(f"Total bytes read: {len(data)}") print(f"First 16 bytes: {data[:16].hex()}")
This reads a binary file (PNG image), first extracting the header then the remaining contents. The hex representation shows binary data clearly.
Binary mode preserves all bytes exactly as they appear in the file, unlike text mode which may perform newline conversions.
Proper error handling is crucial when using low-level I/O. This example demonstrates comprehensive error handling for os.read.
error_handling.py
import os import errno
def safe_read(fd, n): try: data = os.read(fd, n) if not data: print(“Reached end of file”) return None return data except OSError as e: if e.errno == errno.EBADF: print(“Error: Bad file descriptor”) elif e.errno == errno.EINTR: print(“Error: Interrupted system call”) elif e.errno == errno.EIO: print(“Error: I/O error occurred”) else: print(f"Unexpected error: {e}") return None
try: fd = os.open(“example.txt”, os.O_RDONLY) data = safe_read(fd, 1024) if data: print(data.decode(‘utf-8’)) finally: if ‘fd’ in locals(): os.close(fd)
The safe_read function handles various error conditions that os.read might encounter. Each error type gets specific handling.
The finally block ensures the file descriptor is closed even if errors occur. This prevents resource leaks in error scenarios.
Buffer size: Larger chunks reduce system calls but increase memory
Direct I/O: O_DIRECT flag bypasses kernel cache for special cases
Memory mapping: For huge files, mmap may be more efficient
System calls: Each os.read is a system call with overhead
Python overhead: High-level file objects add buffering
Always close descriptors: Use try/finally or context managers
Handle partial reads: Check return value length
Use appropriate size: Balance between calls and memory
Consider alternatives: For text, built-in open() is simpler
Watch for EINTR: System calls can be interrupted
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Python tutorials.