11

假设我有一些非常大的 Python 类,可能会消耗大量内存。该类有一些方法负责在解释器退出时清理一些东西,并在 atexit 模块中注册:

import atexit
import os

class ReallyBigClass(object):
    def __init__(self, cache_file):
        self.cache_file = open(cache_file)
        self.data = <some large chunk of data>
        atexit.register(self.cleanup)

    <insert other methods for manipulating self.data>

    def cleanup(self):
        os.remove(self.cache_file)

这个类的各种实例可能会在程序的整个生命周期中来来去去。我的问题是:

如果我说,del我对实例的所有其他引用,使用 atexit 注册实例方法是否安全?换句话说,是否atexit.register()以与传统绑定相同的方式增加引用计数器?如果是这样,整个类实例现在是否必须在内存中徘徊并等到退出,因为它的方法之一已向 atexit 注册,或者实例的一部分是否可以被垃圾收集?对于像这样的瞬态类实例,在退出时构建这种清理的首选方法是什么,以便可以有效地进行垃圾收集?

4

2 回答 2

17

注册一个实例方法atexit会使整个类实例持续存在,直到解释器退出。解决方案是将注册的任何函数与atexit类分离。然后可以成功地对实例进行垃圾收集。例如,

import atexit
import os
import gc
import random

class BigClass1(object):
    """atexit function tied to instance method"""
    def __init__(self, cache_filename):
        self.cache_filename = cache_filename
        self.cache_file = open(cache_filename, 'wb')
        self.data = [random.random() for i in range(10000000)]
        atexit.register(self.cleanup)

    def cleanup(self):
        self.cache_file.close()
        os.remove(self.cache_filename)

class BigClass2(object):
    def __init__(self, cache_filename):
        """atexit function decoupled from instance"""
        self.cache_filename = cache_filename
        cache_file = open(cache_filename, 'wb')
        self.cache_file = cache_file
        self.data = [random.random() for i in range(10000000)]

        def cleanup():
            cache_file.close()
            os.remove(cache_filename)

        atexit.register(cleanup)

if __name__ == "__main__":
    import pdb; pdb.set_trace()

    big_data1 = BigClass1('cache_file1')
    del big_data1
    gc.collect()

    big_data2 = BigClass2('cache_file2')
    del big_data2
    gc.collect()

逐行遍历这一行并监视进程内存表明,被占用的内存big_data1一直保持到解释器退出,而big_data2在之后成功进行垃圾收集del。单独运行每个测试用例(注释掉另一个测试用例)提供相同的结果。

于 2013-08-15T17:39:26.153 回答
1

我对实现的想象atexit如下:

try:
    # Your whole program
finally:
    if sys.exitfunc:
        sys.exitfunc()

并且模块atexit只是将 sys.exitfunc 设置为自己的回调。因此,您无需担心由于解释器关闭导致的任何清理而导致的任何对象消失。

注意 1:没有指定如果 sys.exitfunc 调用会发生什么,sys.exit(EXIT_CODE)但在我的情况下会返回 'EXIT_CODE'

注意 2:atexit按顺序执行所有注册函数并捕获所有异常,因此它也隐藏 sys.exit 执行(因为它只是引发 SystemExit 异常)

于 2013-05-02T08:25:28.690 回答