Python os.closerange 函数
上次修改时间:2025 年 4 月 11 日
这份全面的指南探讨了 Python 的 os.closerange 函数,它能有效地关闭一系列文件描述符。我们将介绍它的参数、用例和资源管理的实用示例。
基本定义
os.closerange 函数关闭从 fd_low (包含) 到 fd_high (不包含) 的所有文件描述符。它会忽略关闭期间的错误。
主要参数:fd_low (要关闭的第一个描述符),fd_high (停止于此描述符之前)。返回 None。属于 Python 的 os 模块,用于与操作系统交互。
关闭一系列文件描述符
这个基本示例演示了如何关闭描述符 3 到 9。该函数将尝试关闭描述符 3、4、5、6、7 和 8。
import os
# Open several files to get multiple descriptors
files = [open(f"file_{i}.txt", "w") for i in range(10)]
# Get their file descriptors
fds = [f.fileno() for f in files]
print(f"Open descriptors before: {fds}")
# Close descriptors 3 through 9 (3-8)
os.closerange(3, 9)
# Verify which descriptors are still open
remaining_fds = [f.fileno() for f in files[:3] + files[9:]]
print(f"Open descriptors after: {remaining_fds}")
# Clean up remaining files
for f in files[:3] + files[9:]:
f.close()
这将创建 10 个文件,然后使用 closerange 关闭描述符 3 到 8。前 3 个和最后一个文件将保持打开状态,直到显式关闭。
请注意,即使在使用 closerange 关闭其底层描述符后,也应正确关闭文件对象。
关闭高于阈值的所有描述符
一个常见的用例是关闭高于某个数字的所有描述符,通常在 fork() 之后使用,以清理不需要的文件描述符。
import os
import resource
# Get maximum number of open files
max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
# Open several files
files = [open(f"temp_{i}.txt", "w") for i in range(5)]
fds = [f.fileno() for f in files]
print(f"Current open descriptors: {fds}")
# Close all descriptors above 3
os.closerange(3, max_fd)
# Verify closed descriptors
try:
for fd in fds:
if fd >= 3:
os.fstat(fd) # Will raise OSError if closed
except OSError as e:
print(f"Descriptor check failed: {e}")
# Clean up
for f in files:
if not f.closed:
f.close()
此示例关闭从 3 到系统最大值的所有描述符。resource 模块有助于确定关闭操作的上限。
try-except 块演示了如何通过尝试对其进行操作来验证描述符是否已实际关闭。
closerange 的错误处理
当关闭描述符时,os.closerange 会静默地忽略错误。此示例演示了如何围绕它实现自定义错误处理。
import os
def safe_closerange(fd_low, fd_high):
"""Close range with basic error reporting"""
open_fds = []
# Check which descriptors in range are actually open
for fd in range(fd_low, fd_high):
try:
os.fstat(fd)
open_fds.append(fd)
except OSError:
pass
# Perform the close
os.closerange(fd_low, fd_high)
# Verify which descriptors were successfully closed
closed = []
remaining = []
for fd in open_fds:
try:
os.fstat(fd)
remaining.append(fd)
except OSError:
closed.append(fd)
return closed, remaining
# Example usage
closed, remaining = safe_closerange(3, 10)
print(f"Successfully closed: {closed}")
print(f"Still open: {remaining}")
此包装函数通过在 closerange 操作之前和之后检查描述符状态来提供基本的错误报告。
虽然由于潜在的竞争条件而并非万无一失,但它比标准 closerange 行为提供更多的可见性。
在守护程序中使用 closerange
守护进程通常使用 closerange 在 fork 之后清理文件描述符。此示例演示了一个简单的守护进程化模式。
import os
import sys
import time
def daemonize():
# First fork
try:
pid = os.fork()
if pid > 0:
sys.exit(0) # Exit parent
except OSError as e:
sys.stderr.write(f"First fork failed: {e}\n")
sys.exit(1)
# Create new session
os.setsid()
# Second fork
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError as e:
sys.stderr.write(f"Second fork failed: {e}\n")
sys.exit(1)
# Close all open file descriptors
max_fd = os.sysconf('SC_OPEN_MAX')
if max_fd == -1: # Default to 1024 if unknown
max_fd = 1024
os.closerange(0, max_fd)
# Redirect standard file descriptors to /dev/null
devnull = os.open('/dev/null', os.O_RDWR)
for fd in (0, 1, 2): # stdin, stdout, stderr
os.dup2(devnull, fd)
# Daemon main loop
while True:
with open("/tmp/daemon.log", "a") as f:
f.write(f"Daemon running at {time.ctime()}\n")
time.sleep(5)
if __name__ == "__main__":
daemonize()
这显示了一个标准的守护进程化过程,其中 closerange 有助于在守护进程开始工作之前清理所有文件描述符。
该示例包括双重 fork、会话创建以及对标准 I/O 描述符的正确处理。
选择性描述符保留
有时,您需要关闭大多数描述符,但保留一些描述符。此示例演示如何将 closerange 与选择性保留相结合。
import os
import sys
def isolate_process(preserve_fds=[]):
"""Close all descriptors except those specified"""
max_fd = 1024 # Default reasonable maximum
# Close lower range (0 to first preserved fd)
if preserve_fds:
os.closerange(0, min(preserve_fds))
# Close ranges between preserved fds
sorted_fds = sorted(preserve_fds)
for i in range(len(sorted_fds) - 1):
os.closerange(sorted_fds[i] + 1, sorted_fds[i + 1])
# Close upper range (last preserved fd to max)
if sorted_fds:
os.closerange(sorted_fds[-1] + 1, max_fd)
else:
os.closerange(0, max_fd)
# Example usage
log_fd = os.open("preserved.log", os.O_WRONLY | os.O_CREAT)
sock_fd = os.open("/dev/tty", os.O_RDWR)
print(f"Preserving descriptors: {log_fd}, {sock_fd}")
isolate_process(preserve_fds=[log_fd, sock_fd])
# Verify
try:
os.write(log_fd, b"Still open\n")
os.write(sock_fd, b"Still open\n")
print("Preserved descriptors still functional")
except OSError as e:
print(f"Descriptor error: {e}")
# Clean up
os.close(log_fd)
os.close(sock_fd)
此函数关闭除 preserve_fds 中显式列出的描述符之外的所有描述符。它有效地处理保留描述符周围的范围。
该示例保留了一个日志文件和终端描述符,同时关闭假定范围内的所有其他描述符。
可移植的描述符管理
此示例展示了一种更可移植的描述符管理方法,该方法适用于具有不同最大描述符值的不同类 Unix 系统。
import os
import resource
import sys
def portable_descriptor_cleanup(keep_fds=[]):
"""Portable descriptor cleanup across Unix systems"""
try:
# Try to get system maximum
max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
if max_fd == resource.RLIM_INFINITY:
max_fd = 1024 # Fallback value
except (AttributeError, ValueError):
max_fd = 1024 # Default fallback
# Get actually open descriptors
open_fds = set()
for fd in range(max_fd):
try:
os.fstat(fd)
open_fds.add(fd)
except OSError:
pass
# Close all not in keep_fds
for fd in open_fds:
if fd not in keep_fds:
try:
os.close(fd)
except OSError:
pass # Already closed or invalid
# Example usage
important_fd = os.open("critical.log", os.O_WRONLY | os.O_CREAT)
print(f"Preserving descriptor: {important_fd}")
portable_descriptor_cleanup(keep_fds=[important_fd, 0, 1, 2])
# Verify
try:
os.write(important_fd, b"Still working\n")
print("Preserved descriptor functional")
except OSError as e:
print(f"Descriptor error: {e}")
os.close(important_fd)
这种方法比简单的 closerange 更强大,因为它首先检测哪些描述符实际打开,然后再尝试关闭它们。
默认情况下,它会保留标准描述符 (0,1,2) 以及任何显式列出的描述符,使其适合守护程序和安全应用程序。
安全注意事项
- 资源泄漏:未能关闭描述符可能导致泄漏
- 安全风险:意外打开的描述符会带来风险
- 可移植性:不同系统上的最大描述符值不同
- 竞争条件:可以在检查和关闭之间打开描述符
- 静默失败:closerange 不报告哪些关闭失败
最佳实践
- 在守护程序中使用:对于正确的守护进程化至关重要
- 与 dup2 结合使用:首先重定向标准描述符
- 考虑上下文:在托管环境中可能不需要
- 记录保留的 fds:清楚地记录哪些描述符保持打开状态
- 彻底测试:验证目标平台上的行为
资料来源
作者
列出所有 Python 教程。