Complete guide to Python's os.pread function covering file descriptor reading, offset handling, and practical examples.
Last modified April 11, 2025
This comprehensive guide explores Python’s os.pread function, which reads from a file descriptor at a specific offset. We’ll cover file descriptors, offset handling, and practical low-level file I/O examples.
The os.pread function reads data from a file descriptor at a specified offset without changing the file position. It’s thread-safe and useful for random access file operations.
Key parameters: fd (file descriptor), n (number of bytes to read), offset (position to read from). Returns bytes read as a bytes object.
This example demonstrates the simplest use of os.pread to read from a file at a specific offset. We first open a file to get its descriptor.
basic_read.py
import os
with open(“data.txt”, “w”) as f: f.write(“Hello World! This is a test file.”)
fd = os.open(“data.txt”, os.O_RDONLY)
data = os.pread(fd, 5, 6) print(f"Read data: {data.decode()}") # Output: World
os.close(fd)
The code creates a test file, opens it to get a file descriptor, then uses os.pread to read “World” from offset 6. The file position isn’t affected by this operation.
Note we use os.open for low-level file descriptor access and must manually close it with os.close.
os.pread is ideal for reading large files in chunks at specific offsets. This example shows how to process a file in fixed-size blocks.
chunked_read.py
import os
with open(“large.bin”, “wb”) as f: f.write(os.urandom(1024 * 1024))
fd = os.open(“large.bin”, os.O_RDONLY) chunk_size = 4096 # 4KB chunks offset = 0
while True: data = os.pread(fd, chunk_size, offset) if not data: break # End of file
print(f"Read {len(data)} bytes from offset {offset}")
# Process chunk here
offset += len(data)
os.close(fd)
This creates a 1MB random binary file, then reads it in 4KB chunks using os.pread. The offset is manually advanced after each read.
The loop continues until os.pread returns an empty bytes object, indicating end of file. Each read operation is independent and thread-safe.
os.pread is thread-safe as it doesn’t modify the file position. This example demonstrates parallel reading from multiple threads.
threaded_read.py
import os import threading
def read_chunk(fd, offset, size): data = os.pread(fd, size, offset) print(f"Thread {threading.get_ident()}: Read {len(data)} bytes from {offset}")
with open(“parallel.txt”, “w”) as f: f.write(“ABCDEFGHIJKLMNOPQRSTUVWXYZ”)
fd = os.open(“parallel.txt”, os.O_RDONLY)
threads = [] for i in range(0, 26, 5): t = threading.Thread(target=read_chunk, args=(fd, i, 5)) threads.append(t) t.start()
for t in threads: t.join()
os.close(fd)
This creates multiple threads that read different 5-byte chunks from the file simultaneously. Each thread specifies its own offset without interfering with others.
The thread-safety comes from os.pread not using or modifying the shared file position that traditional read operations would use.
This example shows how to use os.pread to read from calculated positions in a structured binary file, similar to a database record access.
structured_read.py
import os import struct
records = [ struct.pack(“i10s”, 1, b"Alice"), struct.pack(“i10s”, 2, b"Bob"), struct.pack(“i10s”, 3, b"Charlie") ] with open(“records.bin”, “wb”) as f: f.write(b"".join(records))
fd = os.open(“records.bin”, os.O_RDONLY) record_size = struct.calcsize(“i10s”)
data = os.pread(fd, record_size, record_size) id, name = struct.unpack(“i10s”, data) print(f"Record 2: ID={id}, Name={name.decode().strip()}")
os.close(fd)
We create a binary file with fixed-size records, then use os.pread to directly access the second record by calculating its offset.
The record size is calculated using struct.calcsize, allowing precise positioning in the file without maintaining a file position pointer.
os.pread may return fewer bytes than requested. This example demonstrates proper handling of partial reads and end-of-file conditions.
partial_read.py
import os
with open(“small.txt”, “w”) as f: f.write(“Short”)
fd = os.open(“small.txt”, os.O_RDONLY)
data = os.pread(fd, 100, 0) print(f"Read {len(data)} bytes: {data.decode()}") # Output: 5 bytes
data = os.pread(fd, 10, 10) print(f"Read {len(data)} bytes from offset 10") # Output: 0 bytes
os.close(fd)
The first read attempts to get 100 bytes but only receives 5 (the file size). The second read starts beyond EOF and returns an empty bytes object.
Applications must always check the returned data length rather than assuming the requested number of bytes was read.
This example contrasts os.pread with traditional file reading methods, showing how file position is unaffected by os.pread.
compare_read.py
import os
with open(“compare.txt”, “w”) as f: f.write(“ABCDEFGHIJKLMNOPQRSTUVWXYZ”)
fd = os.open(“compare.txt”, os.O_RDONLY)
os.lseek(fd, 10, os.SEEK_SET) data1 = os.read(fd, 5) print(f"Traditional read: {data1.decode()}") # KLMNO
data2 = os.pread(fd, 5, 15) print(f"pread result: {data2.decode()}") # PQRST print(f"Current position: {os.lseek(fd, 0, os.SEEK_CUR)}") # Still 15
os.close(fd)
After seeking to position 10, a traditional read advances the position to 15. A subsequent os.pread at offset 15 doesn’t affect the position.
This demonstrates os.pread’s key advantage: reading at specific offsets without disturbing the current file position.
This example shows proper error handling for os.pread, including invalid file descriptors, bad offsets, and interrupted system calls.
error_handling.py
import os import errno
try: # Attempt read from invalid descriptor data = os.pread(9999, 10, 0) except OSError as e: print(f"Error reading: {e.errno} ({errno.errorcode[e.errno]})")
try: fd = os.open(“example.txt”, os.O_RDONLY | os.O_CREAT, 0o644) data = os.pread(fd, 10, -5) # Invalid offset except OSError as e: print(f"Invalid offset error: {e}") finally: if ‘fd’ in locals(): os.close(fd)
The first attempt fails with EBADF (bad file descriptor). The second fails with EINVAL due to negative offset. Always handle such errors gracefully.
Note the use of errno.errorcode to translate numeric error codes to their symbolic names for better error reporting.
Thread safety: pread is atomic and doesn’t affect file position
Kernel calls: Each pread is a separate system call
Buffer size: Larger reads are generally more efficient
Position tracking: No need to manage file position
Caching: OS may cache frequently accessed regions
Check return size: Always verify bytes read
Handle errors: Catch OSError for robust code
Use appropriate size: Balance between many small and few large reads
Close descriptors: Always close file descriptors
Consider alternatives: For simple cases, regular file objects may suffice
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.