今天,在编程时,我发现自己在生成器函数内管理资源(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
以这种方式管理生成器函数内的资源是不好的做法吗?
在我的在这种情况下,我计划从开始到结束始终消耗
yield_fname
的生成器,以便连接始终关闭。我是否应该担心有人(例如未来的我)使用生成器而没有完全消耗它并保持连接打开?有没有办法防止这种情况?
(作为旁注,这里我决定使用生成器函数,因为我想避免循环遍历文件名列表两次或更多次)。
你的代码展示了在 Python 生成器中使用
with
语句管理资源的完美有效且推荐的方式。 事实上,这是处理此类情况的最佳方法之一。
让我们分解一下为何如此以及为何你不必担心资源泄漏:
-
with
语句和上下文管理器:with
语句与上下文管理器(如paramiko.SSHClient
)相结合,可确保即使在发生错误或生成器未完全耗尽的情况下也能正确清理资源。 当执行离开with
块时,无论正常退出还是通过异常退出,都会自动调用上下文管理器的__exit__
方法。 在你的情况下,这会保证关闭 SSH 连接。 -
生成器不会阻止清理: 你是对的,生成器在理论上可以让你在不完全迭代完的情况下停止迭代,从而可能使资源处于打开状态。 但是,
with
语句与生成器的工作方式无关。 即使生成器未完全耗尽,当执行离开with
块时,with
块仍会保证调用清理代码(__exit__
方法)。 -
你的使用场景: 在你的情况下,你提到你始终打算完全消耗生成器。 这很好,但这不会改变
with
语句带来的安全性和便利性。 它充当了一个安全网,即使在未来代码行为发生变化或出现意外异常的情况下也能确保正确清理。
简而言之,在你的生成器函数中使用
with
语句来管理 SSH 连接是一种干净且 Pythonic 的方法,并且你无需担心资源泄漏,即使生成器未完全耗尽也是如此。
with
语句为你提供了保障。
此外,使用生成器来避免多次循环遍历文件名的决定是一个很好的优化,可以提高代码的效率。
如果你想在代码中添加额外的防御层,可以考虑添加一些日志记录或在
__exit__
方法中进行断言,以验证在离开
with
块时资源是否已正确关闭。 但这对于你的情况来说可能有点过分了,因为
with
语句已经为你处理得很好。