从Python中的现有对象删除'__del__'方法

时间:2018-12-24 19:50:15

标签: python

我有一个带有ProcessPoolExecutor的应用程序,向我提供了一个对象实例,该对象实例具有使用__del__方法实现的析构函数。

问题是,__del__方法从磁盘上删除了所有线程(进程)共有的文件。当池中的进程完成其工作时,它将调用其获取的对象的__del__方法,从而破坏了其他线程(进程)的资源。

我试图准备一个没有析构函数的“安全”对象,该对象将在向池中提交作业时使用:

my_safe_object = copy.deepcopy(my_object)
delattr(my_safe_object, '__del__')

但是delattr调用失败,并出现以下错误:

AttributeError: __del__

有什么想法如何在运行时摆脱现有对象的__del__方法?

2 个答案:

答案 0 :(得分:2)

如果您有权访问对象的类代码,那么在此所做的最好的事情就是根本不依赖__del____del__具有永久性副作用这一事实本身可能是一个问题,但是在使用多处理的环境中,这绝对是不行的!

这是为什么:第一个__del__是实例类上的一种方法,就像大多数“魔术”方法一样(这就是为什么您不能从实例中删除它的原因)。其次:当对一个对象的引用达到零时,将调用__del__。但是,如果您在“主”进程上没有对某个对象的任何引用,那并不意味着所有子进程都已结束。这可能是问题的根源:对象的引用计数在每个过程中都是独立的。第三:即使在单个流程应用程序中,您也没有太多时间控制__del__的调用时间。在字典中悬挂引用某个对象或在某个地方缓存它并不难-因此通常不建议将重要的应用程序行为与__del__绑定在一起。所有这些仅适用于最新的Python版本(〜> 3.5),因为在此之前,__del__甚至更加不可靠,Python根本无法确保它被调用。

因此,正如其他答案所说,您可以直接在类上进行尝试暂停__del__,但这必须在所有子过程中的对象类上完成也一样

因此,我建议您执行此操作的方法是要有一个显式调用的方法,该方法将在处理对象时执行文件擦除和其他副作用。您只需重命名__del__方法并仅在主进程中调用它即可。

如果您想确保调用此“析构函数”,Python确实会通过context protocol提供一些自动控制:然后,您将在with语句块中使用您的对象-并将其销毁。在__exit__方法中。在with块的末尾自动调用此方法。当然,您将不得不设计一种方法来使with块仅在实例的子进程中的工作完成时保留。这就是为什么在这种情况下,我认为在消耗掉您在流程外执行的任何“结果”时,将在您的主流程上调用一个普通的,显式的清理方法会更加容易。

TL; DR

  • 将源对象的类清除代码从__del__更改为普通方法,例如cleanup
  • 在将实例提交到流程外执行时,请使用concurrent.futures.as_completed调用在主流程中调用清理。

如果您无法更改该对象的类的源代码,请继承它, 使用no-op方法覆盖__del__,并在将对象提交给其他进程之前强制对象的__class__继承类:

class SafeObject(BombObject):
    def __del__(self):
       pass

def execute(obj):
    # this function is executed in other process
    ...

def execute_all(obj_list):
    executor = concurrent.futures.ProcessPoolExecutor(max_workers=XX)
    with executor:
        futures = {}
        for obj in obj_list:
            obj.__class__ = SafeObject
            futures[executor.submit(execute, obj)] = obj
        for future in concurrent.futures.as_completed(futures):
            value = future.result()  # add try/except aroudn this as needed.
            BombClass.__del__(obj)  # Or just restore the "__class__" if the isntances will be needed elsewhere

    del futures  # Needed to clean-up the extra references to the objects created in the futures dict. 

(请注意,上面的“ with”语句来自于ProcessPoolExecutor的推荐用法,来自文档,而不是我建议您在答案的前面使用的自定义__exit__方法。{{1} }等效的块,它将使您能够充分利用ProcessPoolExecutor的知识,并且需要一定的技巧)

答案 1 :(得分:1)

通常,方法属于此类。通常,您可以在实例上阴影一个方法,但是特殊的“ dunder”方法已进行了优化,无论如何都首先检查该类。因此请考虑:

In [1]: class Foo:
   ...:     def __int__(self):
   ...:         return 42
   ...:

In [2]: foo = Foo()

In [3]: int(foo)
Out[3]: 42

In [4]: foo.__int__ = lambda self: 43

In [5]: int(foo)
Out[5]: 42

您可以在the docs

中了解有关此行为的更多信息
  

对于自定义类,只有在对对象的类型(而不是在对象的实例字典中)进行定义的情况下,才能保证对特殊方法的隐式调用可以正常工作。

我认为,如果您使用multiprocessing,最干净的解决方案是简单地从类派生并覆盖__del__。我担心,除非您在所有进程中使用猴子修补类,否则猴子修补类在多处理中将无法正常工作。不确定pickle的处理方式如何。