【问题标题】:Cannot unregister functions from atexit in python 2.7无法从 python 2.7 中的 atexit 注销函数
【发布时间】:2015-08-19 14:13:23
【问题描述】:

首先,我用flush方法写了一个记录类:

class Recorder
    def __init__(self, buffer_size, path):
        self._big_buffer = np.array(*buffer_size)
        self._path = path
    def push(self, data):
        # insert in self._big_buffer
        # if self._big_buffer is full:
        #     self._flush()
    def flush(self):
        # write buffer to disk (self._path)

然后,我想在退出时刷新:手动停止时,崩溃或任何原因。

所以我用了:

def __init__(self):
    (...)
    atexit.register(self.flush)

而且效果很好。

但是现在,我想用不同的缓冲区大小和不同的路径进行多次录制、停止录制、再次录制。所以我不得不丢弃,然后实例化几个Recorder。它有点工作,但旧的Recorder 的内存(包含一些胖self._big_buffer̀)没有被释放,因为它被atexit 保留。即使我明确调用del。 我不能atexit.unregister(self._flush),因为它只是 Python 3。

我不希望重复使用现有实例,而是丢弃旧实例并创建新实例。

遇到这种情况你会怎么处理?

【问题讨论】:

    标签: python python-2.7 garbage-collection atexit


    【解决方案1】:

    您可以尝试使用对atexit 处理程序的弱引用,这样对象 如果在别处删除则不会保留:

    import atexit
    import weakref
    
    class CallableMethodWeakRef:
        def __init__(self, object, method_name):
            self.object_ref = weakref.ref(object)
            self.method_name = method_name
        def __call__(self):
            object = self.object_ref()
            if object:
                getattr(object, self.method_name)()
    
    class Recorder:
        def __init__(self, *args):
            atexit.register(CallableMethodWeakRef(self, 'flush'))
    
        def flush(self):
            print 'flushing'
    

    该方法作为字符串传递以避免很多问题 绑定方法弱引用,如果您发现它令人不安,您可以随时使用 像这样的BoundMethodWeakref 实现:http://code.activestate.com/recipes/578298-bound-method-weakref/

    【讨论】:

    • 注意:注册的CallableMethodWeakRef对象会被调用,即使底层对象已经被删除。没关系,除非您有大量 Recorder 对象 - 在这种情况下,您可以将此处描述的技术与您自己的自定义 atexit.unregister 方法结合起来,我可以给您一些提示跨度>
    • 我用作为字符串传递的方法尝试了你的食谱,效果很好!谢谢。
    【解决方案2】:

    我会说您尝试使用错误的工具。 with 语句和上下文管理器是一个非常好的工具。文件 IO 是大多数 Python 用户会接触到 with 语句的主要示例。

    f = open("somefile.txt", "w")
    try:
        f.write("...")
        # more file operations
    finally:
        # regardless of what happens, make sure the files is closed 
        f.close()
    

    变成:

    with open("somefile.txt", "w") as f:
        f.write("...")
        # more file operations
    # close automatically called at the end of the block
    

    您可以通过为您的类编写__enter____exit__ 方法来创建自己的上下文管理器。

    class Recorder
        def __init__(self, buffer_size, path):
            self._big_buffer = np.array(*buffer_size)
            self._path = path
        def push(self, data):
            # insert in self._big_buffer
            # if self._big_buffer is full:
            #     self._flush()
        def flush(self):
            # write buffer to disk (self._path)
        def __enter__(self):
            return self
        def __exit__(self, exctype, exception, traceback):
            # If an exception was thrown in the with block you will get the details here. 
            # If you want the say that the exception has been handled and for it not to be 
            # raised outside the with block then return True
            self.flush()
            # self.close() ?
    

    然后您将使用您的 Recorder 对象,例如:

    with Recorder(...) as recorder:
        # operations with recorder
        ...
    # regardless of what happens the recorder will be flushed at this point
    

    【讨论】:

    • 嗯,我知道 with 声明。只是因为应用程序现在的构建方式,我无法使用它。或者也许我仍然可以使用它,但是 1/ 代码会更难看,2/ 很容易忘记使用 with。不过,+1 是一个很好的答案:)
    【解决方案3】:

    答案当然是 允许您的Recorder 随意更改路径和缓冲区特征。你说 “我宁愿不重用现有实例,而是丢弃旧实例并创建新实例。” 但你没有给出任何理由, 除非您假设 “旧 Recorder 的内存(包含一些胖 self._big_buffer̀)没有被释放,因为它被 atexit 保留”,我认为这是不正确的。

    虽然atexit 确实保留了对记录器对象的引用,但这仅意味着只要记录器引用它,缓冲存储器就会被保留。添加close() 方法很容易,例如

        def close(self):
            self.flush()
            self._big_buffer = None
    

    还有宾果游戏!不存在对缓冲内存的引用,它是可回收的。

    您的__init__() 方法应该简单地向atexit 注册,然后open() 方法(它完成了__init__() 当前所做的其余部分)可以多次使用,每次调用后跟一个close() .

    总之,我认为你的问题需要一个对象。

    【讨论】:

    • @Dunes 的答案代表了解决该问题的一种更一般的方法,尽管我怀疑 可能在极少数情况下atexit 捕获了__exit__ 方法的情况上下文管理器可能不会。
    • 问题是,就我而言,对于单个对象,我打算在不久的将来同时使用多个类的实例。 atexit 机制只是在崩溃之前刷新或退出未处理的应用程序的一种方式(顺便说一下它使用 pyqt)
    • 我绝对没有理由认为您不应该拥有尽可能多的实例。虽然atexit 调用确实会保留对它们的引用,但只要您首先调用它们的close() 方法就不会过于昂贵,因为不会保留任何缓冲区引用。如果缓冲区为None,请确保flush() 方法调用立即返回,这样就不会在退出时造成问题。
    【解决方案4】:

    您可以从(未记录的)atexit._exithandlers 列表中手动移除句柄。

    import atexit
    
    def unregister(func, *targs, **kargs):
    
        """unregister a function previously registered with atexit.
           use exactly the same aguments used for before register.
        """
        for i in range(0,len(atexit._exithandlers)):
            if (func, targs, kargs) == atexit._exithandlers[i] :
                del atexit._exithandlers[i]
                return True
        return False
    

    希望对您有所帮助。

    【讨论】:

    • _exithandlers 中的前导下划线是有目的的,因为解决方案看起来像一个 hack,它滥用了 non-documented behavior
    • 如果你想有礼貌,你可以只基于 atexit 代码创建 my_atexit 并像你自己的扩展一样做。 ;)
    • 拜托,你的链接是 py3 的,这个主题是 py2 的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-19
    • 1970-01-01
    • 2010-12-12
    • 2013-10-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多