You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何明确要求调用方关闭生成器,避免未完全消费导致SSH连接泄漏?

生成器中SSH连接的资源安全管理问题

我在编程时用生成器函数管理SSH连接资源,目的是避免两次遍历文件名列表,示例代码如下:

# 用生成器避免两次遍历文件名列表
def _yield_fname(host_address, usr, pwd, datapath):
    with paramiko.SSHClient() as ssh_client:
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(host, username=usr, password=pwd)
        
        stdin, stdout, stderr = ssh_client.exec_command(
            'find . -type d -maxdepth 1 -printf "%f\n"'
        )
        for line in stdout:
            yield fname

def get_foos():
    foos= []
    for fname in self._yield_fname(self.FOO_PATH):
        foos.append(Foo.from_fname(fname))
    return foos

def get_bars():
    bars= []
    for fname in self._yield_fname(self.FOO_PATH):
        bars.append(Bar.from_fname(fname))
    return bars

目前我会完全消费生成器来确保连接关闭,但担心其他人(比如未来的自己)没有完全消费生成器,导致SSH连接无法正常关闭。请问如何明确提示调用方,即使未完全消费生成器,也必须在使用后将其关闭?


可行的解决方案

1. 将生成器包装为上下文管理器

让生成器支持with语句,强制调用方通过上下文管理资源,无论是否完全消费,退出上下文时都会自动清理SSH连接。可以用contextlib.contextmanager装饰器改造:

from contextlib import contextmanager

@contextmanager
def _yield_fname(host_address, usr, pwd, datapath):
    with paramiko.SSHClient() as ssh_client:
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(host, username=usr, password=pwd)
        
        stdin, stdout, stderr = ssh_client.exec_command(
            'find . -type d -maxdepth 1 -printf "%f\n"'
        )
        # 转换为迭代器,方便后续处理剩余输出
        fname_iter = (line.strip() for line in stdout)
        try:
            yield fname_iter
        finally:
            # 读取剩余输出,避免SSH连接因未处理完数据流而阻塞
            for _ in fname_iter:
                pass

调用方式变为:

def get_foos():
    foos = []
    with self._yield_fname(self.FOO_PATH) as fname_iter:
        for fname in fname_iter:
            foos.append(Foo.from_fname(fname))
    return foos

2. 利用生成器关闭机制+文档提示

在生成器内部处理GeneratorExit异常,确保生成器被手动关闭时也能清理资源,同时在函数文档中明确提示调用方规则:

def _yield_fname(host_address, usr, pwd, datapath):
    """
    生成远程主机指定路径下的目录名。
    注意:若未完全迭代生成器,必须手动调用`close()`方法,否则SSH连接可能无法正常关闭。
    """
    ssh_client = paramiko.SSHClient()
    try:
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(host, username=usr, password=pwd)
        
        stdin, stdout, stderr = ssh_client.exec_command(
            'find . -type d -maxdepth 1 -printf "%f\n"'
        )
        for line in stdout:
            yield line.strip()
    except GeneratorExit:
        # 生成器被关闭时,读取剩余输出释放连接
        for _ in stdout:
            pass
    finally:
        ssh_client.close()

3. 封装迭代逻辑,隐藏生成器细节

不直接暴露生成器给调用方,而是提供封装好的处理接口,内部统一确保资源被正确清理:

def _yield_fname(host_address, usr, pwd, datapath):
    # 原生成器实现
    with paramiko.SSHClient() as ssh_client:
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(host, username=usr, password=pwd)
        
        stdin, stdout, stderr = ssh_client.exec_command(
            'find . -type d -maxdepth 1 -printf "%f\n"'
        )
        for line in stdout:
            yield line.strip()

def process_fnames(self, handler):
    """
    批量处理远程路径下的目录名,handler为单个目录名的处理函数
    """
    for fname in self._yield_fname(self.FOO_PATH):
        handler(fname)

def get_foos(self):
    foos = []
    self.process_fnames(lambda fname: foos.append(Foo.from_fname(fname)))
    return foos

def get_bars(self):
    bars = []
    self.process_fnames(lambda fname: bars.append(Bar.from_fname(fname)))
    return bars

内容的提问来源于stack exchange,提问作者Afelium

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 01:44:52