我不明白这个python __del__行为

时间:2011-05-24 00:24:44

标签: python del

有人可以解释为什么以下代码的行为方式如下:

import types

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d1 = Dummy("d1")
del d1
d1 = None
print "after d1"

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, d2)
d2.func()
del d2
d2 = None
print "after d2"

d3 = Dummy("d3")
def func(self):
    print "func called"
d3.func = types.MethodType(func, d3)
d3.func()
d3.func = None
del d3
d3 = None
print "after d3"

输出(注意永远不会调用d2的析构函数)是这个(python 2.7)

delete d1
after d1
func called
after d2
func called
delete d3
after d3

有没有办法“修复”代码,以便在不删除添加的方法的情况下调用析构函数?我的意思是,放置d2.func = None的最佳位置是析构函数!

由于

[编辑]根据前几个答案,我想澄清一点,我不是在询问使用__del__的优点(或缺乏优点)。我试图创建最短的函数,以证明我认为是非直观的行为。我假设已经创建了一个循环引用,但我不确定为什么。如果可能的话,我想知道如何避免循环引用....

7 个答案:

答案 0 :(得分:33)

你不能假设__del__将被调用 - 它不是一个希望资源自动解除分配的地方。如果要确保释放(非内存)资源,则应该使用release()或类似方法,然后显式调用(或在Thanatos指出的context manager中使用它)在下面的评论中。)

至少你应该非常仔细地阅读__del__ documentation,然后你可能不应该尝试使用__del__。 (有关gc.garbage)的其他不良内容,请参阅__del__ documentation

答案 1 :(得分:16)

我提供了自己的答案,因为虽然我很欣赏避免__del__的建议,但我的问题是如何使其正常运行以提供代码示例。

简短版本:以下代码使用weakref来避免循环引用。我想在发布问题之前我已尝试过这个,但我想我一定做错了。

import types, weakref

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, weakref.ref(d2)) #This works
#d2.func = func.__get__(weakref.ref(d2), Dummy) #This works too
d2.func()
del d2
d2 = None
print "after d2"

更长版本: 当我发布问题时,我确实搜索了类似的问题。我知道您可以使用with代替,而普遍的情绪是__del__ BAD

使用with是有道理的,但仅限于某些情况。打开一个文件,阅读它并关闭它是一个很好的例子,with是一个非常好的解决方案。您已经离开了需要该对象的特定代码块,并且您希望清理对象和块的结尾。

数据库连接似乎经常被用作使用with无法正常工作的示例,因为您通常需要保留创建连接的代码部分并在连接中关闭连接更多事件驱动(而非顺序)的时间范围。

如果with不是正确的解决方案,我会看到两种选择:

  1. 您确保__del__有效(请参阅this blog以获得更好的效果) weakref用法的描述)
  2. 在程序关闭时使用atexit模块运行回调。例如,请参阅this topic
  3. 虽然我尝试提供简化代码,但我的真正问题更多是事件驱动的,因此with不是一个合适的解决方案(with对简化代码来说很好)。我还想避免使用atexit,因为我的程序可以长时间运行,并且我希望能够尽快执行清理。

    因此,在这种特定情况下,我发现它是使用weakref的最佳解决方案,并阻止阻止__del__工作的循环引用。

    这可能是规则的一个例外,但有些情况下使用weakref__del__是正确的实现,恕我直言。

答案 2 :(得分:11)

您可以使用with运算符代替 del

http://effbot.org/zone/python-with-statement.htm

就像文件类型对象一样,你可以像

一样
with Dummy('d1') as d:
    #stuff
#d is guaranteed to be out of scope

答案 3 :(得分:8)

del不会致电__del__

del在您使用的方式中删除了一个局部变量。当对象被销毁时调用__del__。 Python作为一种语言并不能保证何时会破坏一个对象。

CPython作为Python最常见的实现,使用引用计数。因此,del通常会按预期工作。但是,如果您有参考周期,它将无法工作。

d3 -> d3.func -> d3

Python没有检测到这一点,所以不会马上清理它。而且它不仅仅是参考周期。如果抛出异常,您可能仍希望仍然调用析构函数。但是,Python通常会将其作为其回溯的一部分保留在局部变量中。

解决方案不依赖于__del__方法。相反,使用上下文管理器。

class Dummy:
   def __enter__(self):
       return self

   def __exit__(self, type, value, traceback):
       print "Destroying", self

with Dummy() as dummy:
    # Do whatever you want with dummy in here
# __exit__ will be called before you get here

这保证可以工作,您甚至可以检查参数以查看是否正在处理异常并在这种情况下执行不同的操作。

答案 4 :(得分:2)

上下文管理器的完整示例。

class Dummy(object):
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        return self
    def __exit__(self, exct_type, exce_value, traceback):
        print 'cleanup:', d
    def __repr__(self):
        return 'Dummy(%r)' % (self.name,)

with Dummy("foo") as d:
    print 'using:', d

print 'later:', d

答案 5 :(得分:2)

在我看来,问题的真正核心在于:

  

添加函数是动态的(在运行时)并且事先不知道

我感觉你真正追求的是一种灵活的方式将不同的功能绑定到一个代表程序状态的对象,也称为多态。 Python做得很好,不是通过附加/分离方法,而是通过实例化不同的类。我建议你再看看你的班级组织。也许您需要将核心持久数据对象与瞬态状态对象分开。使用 has -a 范例而不是 is-a:每次状态更改时,您要么将核心数据包装在状态对象中,要么将新状态对象分配给核心的一个属性。

如果你确定你不能使用那种pythonic OOP,你仍然可以通过在类中定义所有函数开始并以后将它们绑定到其他实例属性来解决你的问题(除非你“从用户输入中动态编译这些函数”:

class LongRunning(object):
    def bark_loudly(self):
        print("WOOF WOOF")

    def bark_softly(self):
        print("woof woof")


while True:
    d = LongRunning()
    d.bark = d.bark_loudly
    d.bark()

    d.bark = d.bark_softly
    d.bark()

答案 6 :(得分:0)

使用weakref的另一种解决方案是仅在通过覆盖类__getattr____getattribute__来调用该函数时才动态绑定该实例以返回func.__get__(self, type(self))对于绑定到实例的函数,而不仅仅是func。这就是在类上定义的函数的行为方式。不幸的是(对于某些用例)python没有为附加到实例本身的函数执行相同的逻辑,但是您可以修改它来执行此操作。我对绑定到实例的描述符有类似的问题。这里的性能可能不如使用weakref那么好,但是它是一个选项,只使用python builtins就可以透明地为任何动态分配的函数工作。

如果您经常发现这种情况,您可能需要一个自定义元类来执行实例级函数的动态绑定。

另一种方法是将函数直接添加到类中,然后在调用时正确执行绑定。对于很多用例,这会引起一些麻烦:即正确地命名函数以使它们不会发生碰撞。但是,实例id可以用于此,因为cPython中的id在程序的整个生命周期中都不是唯一的,所以你需要仔细思考它以确保它适用于你的用例...特别是,当对象超出范围时,您可能需要确保删除类函数,因此它的id / memory地址再次可用。 __del__非常适合这个:)。或者,您可以清除在对象创建时命名为实例的所有方法(在__init____new__中)。

另一种替代方法(而不是搞乱python魔术方法)是显式添加一个方法来调用动态绑定函数。这有一个缺点,即用户无法使用普通的python语法调用您的函数:

class MyClass(object):
    def dynamic_func(self, func_name):
        return getattr(self, func_name).__get__(self, type(self))

    def call_dynamic_func(self, func_name, *args, **kwargs):
        return getattr(self, func_name).__get__(self, type(self))(*args, **kwargs)

    """
    Alternate without using descriptor functionality:
    def call_dynamic_func(self, func_name, *args, **kwargs):
        return getattr(self, func_name)(self, *args, **kwargs)
    """

为了完成这个帖子,我还会显示您的weakref选项:

import weakref
inst = MyClass()
def func(self):
    print 'My func'
#  You could also use the types modules, but the descriptor method is cleaner IMO
inst.func = func.__get__(weakref.ref(inst), type(inst))