循环中open()不close()会触发OSError: [Errno 24] Too many open files,因为每次open()都申请一个文件描述符(fd),Python不自动回收,Linux单进程默认上限1024,超限即报错;with语句虽能自动关闭,但在未捕获异常链中新开文件、全局缓存文件对象、多线程/进程继承fd未清理等三种情况下仍会泄漏。

为什么循环中 open() 不 close() 就会爆 OSError: [Errno 24] Too many open files
因为每次 open() 都会向操作系统申请一个文件描述符(fd),而 Python 默认不自动回收——哪怕文件对象被变量覆盖或函数退出,只要没显式调用 close() 或离开 with 作用域,fd 就一直被进程持有。Linux 单进程默认上限是 1024,循环打开 1025 个文件就直接触发错误。
with open() 真的能完全避免 fd 泄漏吗?
绝大多数情况下可以,但有三个典型例外:
- 在
with块内抛出未捕获异常,且异常处理逻辑里又打开了新文件(比如日志写入),而该日志文件本身也用with打开——这时旧with还没走到__exit__,新with又申请 fd,容易在异常链中累积 - 把
open()返回的文件对象赋值给了全局变量或缓存字典(如日志模块里的_logger_cache),后续没清理,with的自动关闭就失效了 - 在多线程或多进程中,子线程/子进程继承了父进程所有已打开的 fd;若主线程用了
with关闭,但子线程还拿着引用(比如传了 file object 进去),fd 依然被占用
如何快速定位是哪个环节在漏 fd?
别猜,直接看进程当前打开的 fd 数量和来源:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 查总数:
lsof -p <pid> | wc -l</pid>或ls /proc/<pid>/fd | wc -l</pid> - 查类型分布:
lsof -p <pid> | awk '{print $5}' | sort | uniq -c | sort -nr</pid>,重点关注REG(普通文件)、IPv4(网络连接)、PIPE(管道) - 查大头路径:
lsof -p <pid> | grep -E '\.log|\.tmp|\.csv' | head -20</pid>,常能一眼看到重复打开的日志或临时文件 - Python 内部检查:
import resource; resource.getrlimit(resource.RLIMIT_NOFILE)看当前软硬限制,len(os.listdir('/proc/self/fd'))看实际已用数
tempfile.mkstemp 为什么比 NamedTemporaryFile 更容易踩坑?
因为 tempfile.mkstemp() 返回的是一个裸 fd 和路径字符串,它**不返回文件对象**,也不帮你管理生命周期:
立即学习“Python免费学习笔记(深入)”;
- 你必须手动调用
os.close(fd),否则 fd 永远不释放 - 即使你用
os.fdopen(fd, 'w')包装成文件对象,也得确保这个对象最终被close()——而fdopen创建的对象**不支持上下文管理协议**(即不能用with) - 如果在多进程场景下 fork,子进程会继承这个
fd,若父子都没关,一个 fd 被算两次占用 - 对比
NamedTemporaryFile(delete=False):它返回的是标准文件对象,支持with,且close()后 fd 立即释放(只是文件没删)
真正危险的不是“打开太多”,而是“以为关了,其实没关”——尤其当 fd 来自底层系统调用、跨进程传递或混用 os.fdopen 时,很容易在调用栈深处悄悄积累。

















