确保在交互式会话中释放资源

时间:2014-02-15 12:31:37

标签: python resources interactive

我有一个类,其对象使用需要清理的系统资源(例如创建临时文件)。至少,这需要在程序结束时发生。到目前为止,我一直在使用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”的方法?

2 个答案:

答案 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