Python os.pwrite 函数
上次修改时间:2025 年 4 月 11 日
这个综合指南探索了 Python 的 os.pwrite
函数,该函数在特定偏移量处将数据写入文件,而无需更改文件指针。我们将介绍它的参数、行为和实际用例。
基本定义
os.pwrite
函数在指定的偏移量处将字节写入文件描述符。它类似于 os.write
,但不修改文件指针位置。
关键参数:fd (文件描述符),data (要写入的字节),offset (文件中的位置)。返回写入的字节数。需要打开文件以进行写入。
基本文件写入
此示例演示了 os.pwrite
的基本用法,用于在文件中的特定位置写入数据。我们首先创建一个包含一些内容的文件。
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)
这将在文件中位置 8 写入 "NEW "。原始内容 "content" 变为 "NEWtent",因为我们覆盖了其中的一部分。在 pwrite 之后,文件指针保持不变。
请注意,我们使用 os.open
获取文件描述符,因为 pwrite 需要文件描述符,而不是文件对象。
在不同的偏移量处写入
此示例展示了在文件中多个位置写入,同时保持写入之间原始文件指针位置。
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)
每个 pwrite
操作都在指定的偏移量处写入,而不影响其他操作或文件指针。这允许在已知文件位置进行精确修改。
输出显示原始字符串,并在指定位置插入了我们的数字序列。
使用 pwrite 追加
虽然 pwrite
通常用于覆盖,但我们可以通过在文件末尾写入来使用它进行追加。这需要知道文件大小。
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)
这演示了如何通过跟踪文件大小使用 pwrite
追加数据。每次写入都发生在当前内容的末尾。
虽然使用 O_APPEND
的 os.write
对于纯追加来说更简单,但这显示了 pwrite
的灵活性。
处理部分写入
pwrite
可能会写入少于请求的字节数。此示例展示了如何处理部分写入并确保写入完整数据。
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")
这实现了一个写入循环,该循环会一直持续到写入所有数据。它会更新每次部分写入的偏移量和剩余数据。
实际上,磁盘已满的情况很少见,但健壮的代码应处理大数据传输的部分写入。
线程安全的文件写入
当写入在单个文件系统块内时,pwrite
是原子的,这使其可用于线程安全的操作。此示例演示了此属性。
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)
这实现了一个简单的计数器,带有并发增量。虽然不完美(读取和写入之间存在竞争条件),但它演示了 pwrite
的原子写入能力。
对于真正的原子增量,除了 pwrite
之外,还应考虑文件锁定或其他同步方法。
二进制数据操作
pwrite
擅长二进制文件操作。此示例显示了在二进制文件中修改特定字节,同时保持其他字节不变。
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)
这会创建一个具有已知模式的二进制文件,然后使用 pwrite
修改特定的字节范围。文件的其余部分保持不变。
此技术对于二进制文件格式很有用,在这种格式中,您需要修改较大文件中的特定标头或数据结构。
性能比较
此示例比较了在对大文件进行随机访问时 pwrite
与常规 write
的性能。
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")
这会基准测试写入到 1MB 文件中的 1000 个随机位置。pwrite
通常通过避免重复的查找操作来获得更好的性能。
性能差异随着更多随机写入而增长,这使得 pwrite
非常适合某些类数据库操作。
安全注意事项
- 文件描述符: 需要正确的文件描述符管理
- 原子写入: 最大 PIPE_BUF 字节的写入是原子的
- 错误处理: 始终检查返回值以进行部分写入
- 并发: 对于多线程使用是安全的,但需要适当的协调
- 平台支持: 在类 Unix 系统上可用,不在 Windows 上
最佳实践
- 用于随机访问: 非常适合在已知偏移量处写入
- 检查返回值: 验证是否写入了所有字节
- 与 pread 结合使用: 用于读-修改-写循环
- 考虑文件同步: 在需要持久性时使用 fsync
- 更喜欢用于大文件: 比 seek+write 更有效
资料来源
作者
列出所有 Python 教程。