ZetCode

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。

basic_closerange.py
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() 之后使用,以清理不需要的文件描述符。

close_above_threshold.py
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 会静默地忽略错误。此示例演示了如何围绕它实现自定义错误处理。

error_handling.py
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 之后清理文件描述符。此示例演示了一个简单的守护进程化模式。

daemon_example.py
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 与选择性保留相结合。

preserve_descriptors.py
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 系统。

portable_closerange.py
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) 以及任何显式列出的描述符,使其适合守护程序和安全应用程序。

安全注意事项

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。到目前为止,我已经撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 Python 教程