调用列表中元素的析构函数

Call destructor of an element from a list

我有类似的东西:

a = [instance1, instance2, ...]

如果我做一个

del a[1]

instance2 已从列表中删除,但是否调用了 instance2 的 destructor 方法?

我对此很感兴趣,因为我的代码使用了大量内存,我需要释放内存以从列表中删除实例。

如果您的对象占用大量资源并且您希望确保正确释放资源,请使用 with() 结构。依赖析构函数时很容易泄漏资源。有关详细信息,请参阅 this SO post

没有。在列表元素上调用 del 只会从列表中删除对对象的引用,它不会(明确地)对对象本身做任何事情。但是:如果列表中的引用是引用该对象的最后一个,那么现在可以销毁并回收该对象。我认为 "normal" CPython 实现会立即销毁并回收对象,其他变体的行为可能会有所不同。

来自像 c++ 这样的语言(就像我一样),这往往是许多人在第一次学习时发现难以掌握的主题 Python。

底线是:当您执行 del XXX 时,您永远不会*在使用 del 时删除 对象 。您只是在删除一个 对象引用 。但是,实际上,假设没有其他引用指向 instance2 对象,将其从列表中删除将根据需要释放内存。

如果您不理解对象和对象引用之间的区别,请继续阅读。

Python:按值传递,还是按引用传递?

您可能熟悉通过引用或值将参数传递给函数的概念。但是,Python 做事的方式不同。参数总是通过 对象引用 传递。阅读 this article 以获得对这意味着什么的有用解释。

总而言之:这意味着当您将变量传递给函数时,您没有传递变量值的副本(按值传递),也没有传递对象本身 - 即地址内存中的值。您正在传递间接引用内存中保存的值的名称对象。

这与 del...有什么关系?

Well, I'll tell you.

假设你这样做:

def deleteit(thing):
    del thing

mylist = [1,2,3]
deleteit(mylist)

...你认为会发生什么? mylist 是否已从全局命名空间中删除?

答案是否定的:

assert mylist # No error

原因是当您在 deleteit 函数中执行 del thing 时,您只是删除了 对该对象的本地对象引用 。该对象引用仅存在于函数内部。作为侧边栏,您可能会问:是否可以在函数内部从全局命名空间中删除对象引用?答案是肯定的,但是你必须首先声明对象引用是全局命名空间的一部分:

def deletemylist():
    global mylist
    del mylist

mylist = [1,2,3]
deletemylist()
assert mylist #ERROR, as expected

综合起来

现在回到你原来的问题。当在任何名称空间中执行此操作时:

del XXX

...您尚未删除由 XXX 表示的对象。你不能那样做。您只删除了 object reference XXX,它引用了内存中的某个对象。对象本身由 Python memory manager 管理。这是一个非常重要的区别。

请注意,因此,当您覆盖某个对象的 __del__ 方法时,该方法在 对象 [=84= 时被调用] 被删除(不是对象引用!):

class MyClass():
    def __del__(self):
        print(self, "deleted")
        super().__del__()

m = MyClass()
del m

...__del__ 方法中的 print 语句不一定会在您执行 del m 之后立即出现。它只发生在对象本身被删除的时间点,这不取决于你。这取决于 Python 内存管理器。当所有命名空间中的所有对象引用都被删除后,最终会执行 __del__ 方法。但不一定立即。

删除作为列表一部分的对象引用时也是如此,就像在原始示例中一样。当您执行 del a[1] 时,仅删除由 a[1] 表示的对象的对象引用,并且可能会或可能不会立即调用该对象的 __del__ 方法(尽管如前所述,一旦不再有对该对象的引用,它最终将被调用,并且该对象被内存管理器收集为垃圾)。

因此,不建议您将希望在 del mything 上立即发生的事情放在 __del__ 方法中,因为这样可能不会发生。


*我相信永远不会。不可避免地,有人可能会对我的回答投反对票,并发表评论讨论规则的例外情况。但无论如何。