os.open()返回整数型文件描述符fd,直接调用系统open(2),需显式指定flags(如os.O_RDONLY)、配对os.close()防泄漏,且os.read()/os.write()仅接受fd、操作字节、不处理编码或缓冲。

用 os.open() 获取文件描述符而不是 open()
Python 的内置 open() 返回的是高级文件对象,不暴露底层 fd;真正操作 fd 必须用 os.open()。它直接调用系统 open(2),返回整数型 fd,后续所有底层操作都基于它。
常见错误是误传字符串路径给 os.read() 或 os.write() ——它们只接受整数 fd,不是路径或 file 对象。
-
os.open()的 flags 参数必须显式指定,比如os.O_RDONLY、os.O_WRONLY | os.O_CREAT,不能只写数字 - mode 参数仅在创建文件时生效(如
0o644),且必须是八进制字面量,写644会被解释为十进制,权限不对 - 打开后记得配对调用
os.close(fd),否则 fd 泄漏,Linux 默认单进程最多 1024 个 fd
os.read() 和 os.write() 的行为边界
这两个函数是纯字节操作,不带缓冲、不识别换行、不处理编码。读写单位是字节长度,返回值是实际操作的字节数(可能小于请求长度,尤其对管道、终端或网络 socket)。
典型陷阱:循环读取时忽略返回值,导致截断或死锁。例如 os.read(fd, 1024) 在 EOF 时返回空 bytes,但若没检查就继续读,会阻塞(除非 fd 设了 os.O_NONBLOCK)。
立即学习“Python免费学习笔记(深入)”;
- 读取时建议用 while 循环 + 检查返回值:
data = os.read(fd, 4096); if not data: break -
os.write()不保证一次写完全部数据,返回值可能小于len(buf),需手动循环补写 - 不能对 stdin/stdout/stderr 的 fd(0/1/2)直接用
os.write()输出 Unicode 字符串——必须先 encode 成 bytes
fd 复制与继承控制:os.dup()、os.closerange() 和 inheritable
子进程默认继承父进程所有打开的 fd,这常导致意外句柄泄漏或权限问题。Python 3.4+ 可通过 os.set_inheritable(fd, False) 关闭继承性;更早版本只能靠 os.closerange(3, 1024) 粗暴关闭高 fd。
os.dup() 和 os.dup2() 用于复制 fd,区别在于后者可指定新 fd 编号(如重定向 stdout 到文件:用 os.dup2(file_fd, 1))。
- 复制后的 fd 共享文件偏移和状态标志(如
O_APPEND),但关闭其中一个不影响另一个 - 用
os.dup2()重定向前,确保目标 fd(如 1)已关闭,否则可能覆盖错误 fd - 多线程中操作 fd 是安全的,但多个线程同时读写同一 fd 会导致数据交错——应用层需自行同步
何时该用 fd 操作而非高级文件对象?
绝大多数场景不需要碰 fd。只有当你要做以下事情时才值得绕过 io 模块:
- 调用特定系统调用,如
os.fsync(fd)强制刷盘、os.posix_fadvise()预读提示、os.flock()文件锁 - 对接非 Python 生态的 C 库,它要求传入原始 fd(比如某些数据库驱动或零拷贝网络库)
- 实现自定义缓冲策略,或需要精确控制 read/write 原子性(如避免
print()自动加 \n 干扰二进制协议)
fd 操作没有编码、换行转换、universal newlines 这些便利特性,出错时错误信息也更底层(比如 OSError: [Errno 9] Bad file descriptor),调试成本明显更高。


















