Python中open().read()后文件描述符会被垃圾回收吗?
我来帮你理清这两个关于Python文件描述符的问题,结合你做的测试来解释清楚:
问题1:执行file_data = open(...).read()时,仅获得指向read()返回数据的file_data,未持有文件描述符,此说法是否正确?
这个说法是完全正确的。当你写open(...).read()时,open()会先创建一个临时的文件对象——这个对象才是持有操作系统文件描述符的载体。紧接着调用read()把文件内容读取成字符串后,这个临时文件对象就没有任何变量去引用它了。Python的垃圾回收机制会很快清理掉这个无引用的文件对象,在清理过程中会自动调用文件对象的关闭逻辑,释放对应的文件描述符。
而最终赋值给file_data的,只是read()返回的字符串数据,它和文件描述符没有任何关联,也不会持有任何操作系统层面的文件资源。
问题2:若文件描述符链接数为0,是否会被垃圾回收?还是文件描述符仍有1个链接需手动关闭?
首先得区分两个层面的概念:Python的垃圾回收是针对Python对象的,而文件描述符是操作系统层面的资源。
当Python的文件对象引用计数降到0时,垃圾回收会触发它的销毁逻辑,其中就包括向操作系统发送关闭文件描述符的请求。而操作系统层面的文件描述符有自己的引用计数(也就是你说的链接数),当这个计数降到0时,操作系统会自动释放该描述符对应的所有资源。
在单进程的Python场景下,如果你没有手动关闭文件,只要文件对象被垃圾回收,对应的文件描述符就会被关闭,其操作系统层面的链接数也会降到0,后续由操作系统处理资源释放。不过这里要提醒一句:依赖垃圾回收来关闭文件并不靠谱——比如遇到循环引用的情况,垃圾回收可能延迟触发,导致文件描述符被长时间占用,甚至耗尽系统的文件描述符上限。所以最佳实践永远是手动调用close(),或者用with语句(with open(...) as f: ...),它会在代码块结束后自动安全地关闭文件。
附加测试验证
你做的lsof测试正好完美验证了第一个问题的结论:
- 当执行
data = open("foo.txt")并停在断点时,lsof foo.txt能看到Python进程持有该文件的文件描述符(FD列的5r表示这是一个只读的文件描述符):
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME python 17249 q 5r REG 8,3 0 1443322 foo.txt
这是因为data变量一直引用着文件对象,文件对象没被回收,所以文件描述符持续被持有。
- 当执行
data = open("foo.txt").read()并停在断点时,lsof foo.txt没有结果。原因就是open()创建的临时文件对象在read()执行后就没了引用,被Python快速回收,文件描述符已经被关闭,操作系统层面也就没有进程再持有这个文件的描述符了。
内容的提问来源于stack exchange,提问作者elektruver

