我有一个类,其对象使用需要清理的系统资源(例如创建临时文件)。至少,这需要在程序结束时发生。到目前为止,我一直在使用contextmanager
来执行此操作,例如
@contextlib.contextmanager
def tempdir(prefix='tmp'):
tmpdir = tempfile.mkdtemp(prefix=prefix)
try:
yield tmpdir
finally:
shutil.rmtree(tmpdir)
with tempdir() as tmp:
do_something(tmp)
这样可以确保在我完成资源后立即释放资源,这对我的大部分目的都有效。但是,我发现这个模型在交互式使用python时会崩溃,例如在ipython
中。在这种情况下,整个会话需要在with
块内,这不是很实用(实际上,ipython
只会一次评估所有块,因此整个交互性将消失)
有没有办法确保资源清理,同时仍然可以在交互式会话中使用?在C++
中,这可以通过将清理放在析构函数中来实现。 python __del__
语句类似,但我被告知__del__
不可靠,应该避免。特别是it is not guaranteed that __del__
will be called for objects that still exist when the interpreter exits ..这是__del__
仍然是最佳解决方案的情况之一吗?如果contextmanager
方法与交互式会话不兼容,为什么建议将其作为最“pythonic”的方法?
答案 0 :(得分:2)
我认为你的问题没有灵丹妙药,但你可以通过以下方式解决它:
显式调用处理资源的方法(您的close
方法)。唯一的缺点是明确性。
为交互式解释器创建一个瘦包装器,使用atexit
module在退出时注册实例的close
方法。缺点是你的所有资源通常都会在你想要的时候发布。
创建辅助函数(动态创建它们并不难),包装资源的使用。如果您需要使用一个资源调用更多函数,则这是不可行的。例如:
def do_something()
with tempdir() as tmp:
return original.do_something(tmp)
创建一个隐藏资源处理的解决方案。例如,我不关心TCP套接字,ssl,301/302重定向,打开证书文件等,我只需要使用一个特定证书通过https发送GET请求。 当然,这取决于你想解决的问题。
答案 1 :(得分:1)
我最终遵循了prokopst的atexit
建议,并定义了这个类装饰器:
import atexit
_toclean_ = set()
def call_exit_for_objects(objs):
"""Calls __exit__ for all objects in objs, leaving objs empty."""
while len(objs) > 0:
obj = objs.pop()
obj.__exit__(None,None,None)
atexit.register(call_exit_for_objects, _toclean_)
def autoclean(cls):
global _toclean_
# Fail on purpose if __init__ and __exit__ don't exist.
oldinit = cls.__init__
oldexit = cls.__exit__
def newinit(self, *args, **kwargs):
oldinit(self, *args, **kwargs)
_toclean_.add(self)
def newexit(self, type, value, traceback):
try: _toclean_.remove(self)
except KeyError: pass
oldexit(self, type, value, traceback)
cls.__init__ = newinit
cls.__exit__ = newexit
return cls
有了这个,我可以拥有一个支持with
语法和交互式的类。例如,对于上面的tmpdir类,我将其重新定义为:
@autoclean
class tempdir
def __init__(self, prefix='tmp'):
self.dir = tempfile.mkdtemp(prefix=prefix)
def close(self): shutil.rmtree(self.dir)
def __enter__(self): return self
def __exit__(self, type, value, traceback): self.close()
def __str__(self): return self.dir
然后将其用作:
with tempdir() as tmp:
do_something(tmp)
或
tmp = tempdir()
do_something(tmp)
tmp.close() # If this is skipped, @autoclean ensures it
still happens when python exits