Python os.pread Function
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.
Basic Definitions
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.
Basic File Reading
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.
import os # Create a test file with open("data.txt", "w") as f: f.write("Hello World! This is a test file.") # Open file and get descriptor fd = os.open("data.txt", os.O_RDONLY) # Read 5 bytes from offset 6 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
.
Reading Large Files in Chunks
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.
import os # Create a large test file (1MB) 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.
Thread-Safe Parallel Reading
os.pread
is thread-safe as it doesn't modify the file position.
This example demonstrates parallel reading from multiple threads.
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}") # Create test file with open("parallel.txt", "w") as f: f.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ") fd = os.open("parallel.txt", os.O_RDONLY) # Create threads to read different sections 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.
Reading from Specific File Positions
This example shows how to use os.pread
to read from calculated
positions in a structured binary file, similar to a database record access.
import os import struct # Create structured binary file 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") # Read second record 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.
Handling Partial Reads
os.pread
may return fewer bytes than requested. This example
demonstrates proper handling of partial reads and end-of-file conditions.
import os # Create small test file with open("small.txt", "w") as f: f.write("Short") fd = os.open("small.txt", os.O_RDONLY) # Attempt to read more bytes than available data = os.pread(fd, 100, 0) print(f"Read {len(data)} bytes: {data.decode()}") # Output: 5 bytes # Read beyond EOF 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.
Comparing with Regular File Reading
This example contrasts os.pread
with traditional file reading
methods, showing how file position is unaffected by os.pread
.
import os # Create test file with open("compare.txt", "w") as f: f.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ") fd = os.open("compare.txt", os.O_RDONLY) # Traditional read (affects position) os.lseek(fd, 10, os.SEEK_SET) data1 = os.read(fd, 5) print(f"Traditional read: {data1.decode()}") # KLMNO # pread doesn't affect position 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.
Error Handling
This example shows proper error handling for os.pread
, including
invalid file descriptors, bad offsets, and interrupted system calls.
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]})") # Valid file but bad offset 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.
Performance Considerations
- 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
Best Practices
- 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
Source References
Author
List all Python tutorials.