Python os.pread 函数
上次修改时间:2025 年 4 月 11 日
本综合指南探讨了 Python 的 os.pread 函数,它从文件描述符的特定偏移量处读取数据。我们将涵盖文件描述符、偏移量处理以及实际的底层文件 I/O 示例。
基本定义
os.pread 函数从指定偏移量的文件描述符读取数据,而不更改文件位置。它是线程安全的,并且对于随机访问文件操作很有用。
关键参数:fd(文件描述符),n(要读取的字节数),offset(从中读取的位置)。将读取的字节作为字节对象返回。
基本文件读取
此示例演示了 os.pread 的最简单用法,即从文件中的特定偏移量读取数据。我们首先打开一个文件以获取其描述符。
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)
该代码创建一个测试文件,打开它以获取文件描述符,然后使用 os.pread 从偏移量 6 读取“World”。文件位置不受此操作的影响。
请注意,我们使用 os.open 进行底层文件描述符访问,并且必须使用 os.close 手动关闭它。
以块读取大型文件
os.pread 非常适合以特定偏移量按块读取大型文件。此示例演示了如何以固定大小的块处理文件。
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)
这将创建一个 1MB 的随机二进制文件,然后使用 os.pread 以 4KB 的块读取它。每次读取后,偏移量都会手动前进。
该循环持续到 os.pread 返回一个空的字节对象,表示文件结束。每个读取操作都是独立的且线程安全的。
线程安全的并行读取
os.pread 是线程安全的,因为它不会修改文件位置。此示例演示了从多个线程并行读取。
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)
这将创建多个线程,这些线程同时从文件中读取不同的 5 字节块。每个线程都指定自己的偏移量,而不会干扰其他线程。
线程安全性来自 os.pread 不使用或修改传统读取操作将使用的共享文件位置。
从特定文件位置读取
此示例演示了如何使用 os.pread 从结构化二进制文件中读取计算出的位置,类似于数据库记录访问。
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)
我们创建一个具有固定大小记录的二进制文件,然后使用 os.pread 通过计算其偏移量来直接访问第二个记录。
记录大小使用 struct.calcsize 计算,从而可以在文件中精确定位,而无需维护文件位置指针。
处理部分读取
os.pread 可能会返回少于请求的字节数。此示例演示了如何正确处理部分读取和文件结束条件。
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)
第一次读取尝试获取 100 个字节,但仅接收 5 个字节(文件大小)。第二次读取从 EOF 之外开始,并返回一个空的字节对象。
应用程序必须始终检查返回的数据长度,而不是假设读取了请求的字节数。
与常规文件读取比较
此示例将 os.pread 与传统文件读取方法进行对比,显示 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)
在寻址到位置 10 之后,传统的读取会将位置推进到 15。在偏移量 15 处进行的后续 os.pread 不会影响该位置。
这证明了 os.pread 的关键优势:在特定偏移量处读取数据,而不会干扰当前文件位置。
错误处理
此示例显示了 os.pread 的正确错误处理,包括无效的文件描述符、错误的偏移量和中断的系统调用。
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)
第一次尝试因 EBADF(错误的文件描述符)而失败。第二次尝试因 EINVAL(由于负偏移量)而失败。始终妥善处理此类错误。
请注意,使用 errno.errorcode 将数字错误代码转换为其符号名称,以便更好地进行错误报告。
性能注意事项
- 线程安全: pread 是原子的,不影响文件位置
- 内核调用: 每个 pread 都是一个单独的系统调用
- 缓冲区大小: 通常,较大的读取效率更高
- 位置跟踪: 无需管理文件位置
- 缓存: 操作系统可能会缓存频繁访问的区域
最佳实践
- 检查返回大小: 始终验证读取的字节数
- 处理错误: 捕获 OSError 以获得健壮的代码
- 使用适当的大小: 在许多小读取和少量大读取之间取得平衡
- 关闭描述符: 始终关闭文件描述符
- 考虑替代方案: 对于简单情况,常规文件对象可能就足够了
资料来源
作者
列出所有 Python 教程。