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

3

我有一个类,它的对象使用系统资源(例如创建临时文件),需要进行清理。至少在程序结束时需要进行清理。到目前为止,我一直在使用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__,因为它不可靠。特别地,不能保证解释器退出时还存在的对象会调用__del__。那么,在这种情况下,__del__是否仍然是最佳解决方案?如果contextmanager方法与交互式会话不兼容,为什么它被推荐作为最常用的“Pythonic”方法呢?
2个回答

2
我不认为有一个完美的解决方案来解决你的问题,但你可以通过以下方式在一定程度上解决它:
  • explicitly calling method to dispose resources (your close method). The only drawback is, well, the explicitness.

  • creating a thin wrapper for interactive interpreter, that registers instance's close method at exit using atexit module. Drawback is that all of your resources will be released usually later than you would like.

  • creating helper functions (it's not hard to create them dynamically), that wraps usage of resources. It's not feasible if you need to call more functions with one resource. For example:

    def do_something()
        with tempdir() as tmp:
            return original.do_something(tmp)
    
  • creating a solution that would hide resource handling. For example I don't care about TCP sockets, ssl, 301/302 redirection, opening certificate file etc., I just need to send a GET request over https using one specific certificate. Naturally it depends on problem you would like to solve.


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接