Python os.pwrite Function
Last modified April 11, 2025
This comprehensive guide explores Python's os.pwrite
function,
which writes data to a file at a specific offset without changing the file
pointer. We'll cover its parameters, behavior, and practical use cases.
Basic Definitions
The os.pwrite
function writes bytes to a file descriptor at a
specified offset. It's similar to os.write
but doesn't modify
the file pointer position.
Key parameters: fd (file descriptor), data (bytes to write), offset (position in file). Returns number of bytes written. Requires file opened for writing.
Basic File Writing
This example demonstrates basic usage of os.pwrite
to write data
at a specific position in a file. We first create a file with some content.
import os # Create a file with initial content with open("data.txt", "w") as f: f.write("Initial content") # Open file for writing and get file descriptor fd = os.open("data.txt", os.O_RDWR) # Write at offset 8 without changing file pointer bytes_written = os.pwrite(fd, b"NEW ", 8) print(f"Wrote {bytes_written} bytes") # Verify content os.lseek(fd, 0, os.SEEK_SET) print(os.read(fd, 100)) # Output: b'Initial NEWtent' os.close(fd)
This writes "NEW " at position 8 in the file. The original content "content" becomes "NEWtent" because we overwrote part of it. The file pointer remains unchanged after pwrite.
Notice we use os.open
to get a file descriptor, as pwrite
requires a file descriptor, not a file object.
Writing at Different Offsets
This example shows writing at multiple positions in a file while maintaining the original file pointer position between writes.
import os # Create a sample file with open("records.txt", "w") as f: f.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ") fd = os.open("records.txt", os.O_RDWR) # Get initial position initial_pos = os.lseek(fd, 0, os.SEEK_CUR) print(f"Initial position: {initial_pos}") # Write at various offsets os.pwrite(fd, b"123", 5) # Overwrite positions 5-7 os.pwrite(fd, b"456", 10) # Overwrite positions 10-12 os.pwrite(fd, b"789", 20) # Overwrite positions 20-22 # Verify position hasn't changed current_pos = os.lseek(fd, 0, os.SEEK_CUR) print(f"Position after writes: {current_pos}") # Read entire file os.lseek(fd, 0, os.SEEK_SET) print(os.read(fd, 100)) # Output: b'ABCDE123HIJ456NOPQRST789VWXYZ' os.close(fd)
Each pwrite
operation writes at the specified offset without
affecting the others or the file pointer. This allows precise modifications
at known file locations.
The output shows the original string with our inserted number sequences at the specified positions.
Appending with pwrite
While pwrite
is typically for overwriting, we can use it to
append by writing at the end of file. This requires knowing the file size.
import os # Create initial file with open("log.txt", "w") as f: f.write("Log starts here\n") fd = os.open("log.txt", os.O_RDWR) # Get file size for append file_size = os.lseek(fd, 0, os.SEEK_END) # Append new entries without moving pointer os.pwrite(fd, b"Entry 1\n", file_size) file_size += len("Entry 1\n") os.pwrite(fd, b"Entry 2\n", file_size) file_size += len("Entry 2\n") os.pwrite(fd, b"Entry 3\n", file_size) # Verify content os.lseek(fd, 0, os.SEEK_SET) print(os.read(fd, 1000).decode()) os.close(fd)
This demonstrates how to append data using pwrite
by tracking
the file size. Each write goes at the end of the current content.
While os.write
with O_APPEND
is simpler for pure
appending, this shows pwrite
's flexibility.
Handling Partial Writes
pwrite
may write fewer bytes than requested. This example shows
how to handle partial writes and ensure complete data is written.
import os fd = os.open("partial.dat", os.O_RDWR | os.O_CREAT) large_data = b"X" * 1000000 # 1MB of data offset = 0 remaining = len(large_data) while remaining > 0: written = os.pwrite(fd, large_data[-remaining:], offset) if written == 0: # Disk full? raise IOError("Failed to write data") offset += written remaining -= written print(f"Wrote {written} bytes, {remaining} remaining") os.close(fd) # Verify print(f"Final file size: {os.path.getsize('partial.dat')} bytes")
This implements a write loop that continues until all data is written. It updates the offset and remaining data with each partial write.
In practice, disk full situations are rare, but robust code should handle partial writes for large data transfers.
Thread-Safe File Writing
pwrite
is atomic when the writes are within a single filesystem
block, making it useful for thread-safe operations. This example demonstrates
this property.
import os import threading fd = os.open("counter.txt", os.O_RDWR | os.O_CREAT) os.write(fd, b"0") # Initialize counter os.lseek(fd, 0, os.SEEK_SET) def increment_counter(thread_id): for _ in range(1000): # Read current value current = int(os.pread(fd, 1, 0)) # Write incremented value os.pwrite(fd, str(current + 1).encode(), 0) threads = [] for i in range(10): t = threading.Thread(target=increment_counter, args=(i,)) threads.append(t) t.start() for t in threads: t.join() os.lseek(fd, 0, os.SEEK_SET) print(f"Final counter value: {os.read(fd, 10).decode()}") os.close(fd)
This implements a simple counter with concurrent increments. While not perfect
(race conditions exist between read and write), it demonstrates pwrite
's
atomic write capability.
For true atomic increments, consider file locking or other synchronization
methods in addition to pwrite
.
Binary Data Manipulation
pwrite
excels at binary file manipulation. This example shows
modifying specific bytes in a binary file while leaving others unchanged.
import os # Create a binary file with a pattern with open("data.bin", "wb") as f: f.write(bytes(range(256))) # 0x00 to 0xFF fd = os.open("data.bin", os.O_RDWR) # Modify specific bytes os.pwrite(fd, b"\xFF\xFF", 0x10) # Overwrite positions 0x10-0x11 os.pwrite(fd, b"\xAA\xBB", 0x80) # Overwrite positions 0x80-0x81 # Verify changes os.lseek(fd, 0, os.SEEK_SET) content = os.read(fd, 256) print(f"Byte at 0x10: {hex(content[0x10])}") print(f"Byte at 0x11: {hex(content[0x11])}") print(f"Byte at 0x80: {hex(content[0x80])}") print(f"Byte at 0x81: {hex(content[0x81])}") os.close(fd)
This creates a binary file with a known pattern, then modifies specific
byte ranges using pwrite
. The rest of the file remains unchanged.
This technique is useful for binary file formats where you need to modify specific headers or data structures within a larger file.
Performance Comparison
This example compares pwrite
performance with regular write
when doing random access to a large file.
import os import time import random def test_pwrite(fd, positions): start = time.time() for pos in positions: os.pwrite(fd, b"X", pos) return time.time() - start def test_write(fd, positions): start = time.time() for pos in positions: os.lseek(fd, pos, os.SEEK_SET) os.write(fd, b"X") return time.time() - start # Create a large test file fd = os.open("perf_test.dat", os.O_RDWR | os.O_CREAT) os.ftruncate(fd, 1000000) # 1MB file # Generate random positions positions = [random.randint(0, 999999) for _ in range(1000)] # Test pwrite pwrite_time = test_pwrite(fd, positions) # Test regular write write_time = test_write(fd, positions) os.close(fd) print(f"pwrite time: {pwrite_time:.4f} seconds") print(f"write time: {write_time:.4f} seconds") print(f"Ratio: {write_time/pwrite_time:.2f}x")
This benchmarks writing to 1000 random positions in a 1MB file. pwrite
typically performs better by avoiding repeated seek operations.
The performance difference grows with more random writes, making pwrite
ideal for certain database-like operations.
Security Considerations
- File descriptors: Requires proper file descriptor management
- Atomic writes: Writes up to PIPE_BUF bytes are atomic
- Error handling: Always check return value for partial writes
- Concurrency: Safe for multi-threaded use with proper coordination
- Platform support: Available on Unix-like systems, not Windows
Best Practices
- Use for random access: Ideal for writing at known offsets
- Check return values: Verify all bytes were written
- Combine with pread: For read-modify-write cycles
- Consider file sync: Use fsync for durability when needed
- Prefer for large files: More efficient than seek+write
Source References
Author
List all Python tutorials.