为什么会调用Python的析构函数?

5
当我在解释器中输入这个命令时,调用“y”似乎会触发析构函数?
class SmartPhone:
    def __del__(self):
       print "destroyed"

y = SmartPhone()
y  #prints destroyed, why is that?
y  #object is still there

这里有一个运行,输出对我来说没有意义。

C:\Users\z4>python
Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> class SmartPhone:
...     def __del__(self):
...        print "destroyed"
...
>>> y = SmartPhone()
>>> del y
destroyed
>>> y = SmartPhone()
>>> y
<__main__.SmartPhone instance at 0x01A7CBC0>
>>> y
<__main__.SmartPhone instance at 0x01A7CBC0>
>>> y
<__main__.SmartPhone instance at 0x01A7CBC0>
>>> del y
>>> y = SmartPhone()
>>> y
destroyed
<__main__.SmartPhone instance at 0x01A7CB98>
>>>

还有一个问题,有时调用“del y”会调用析构函数,有时则不会。

C:\Users\z4>python
Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> class SmartPhone:
...     def __del__(self):
...             print "destroyed"
...
>>>
>>> y = SmartPhone()
>>>
>>> y
<__main__.SmartPhone instance at 0x01B6CBE8>
>>> y
<__main__.SmartPhone instance at 0x01B6CBE8>
>>> y
<__main__.SmartPhone instance at 0x01B6CBE8>
>>> del y
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> y = SmartPhone()
>>> y
destroyed
<__main__.SmartPhone instance at 0x01B6CC38>
>>> del y
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>>

14
无法复制。 - Ignacio Vazquez-Abrams
1
你能发布脚本的实际输出吗? - thegrinner
你确定它在这一行打印出“destroyed”,而不是在脚本结束时? - Dahaka
看起来在那个例子中,打印只是偏了一行,这似乎并不奇怪。 - Slater Victoroff
只需按照精确的顺序输入即可复制它。 - ppone
5个回答

15

这里提到的输出只在交互式shell中有复制。

在交互式会话中,还存在一个名为_的额外变量,它指向上一个值。

使用sys.getrefcount检查引用计数:

>>> import sys
>>> class SmartPhone:
...     def __del__(self):
...        print "destroyed"
...
>>> y = SmartPhone()
>>> sys.getrefcount(y) # not printed, _ does not reference SmartPhone object yet.
2
>>> y
<__main__.SmartPhone instance at 0x000000000263B588>
>>> sys.getrefcount(y) # y printed, _ reference SmartPhone object.
3

2, 3在上面的输出中应该是1, 2。它们被打印成这样,是因为正如getrefcount文档中所提到的那样,getrefcount()会暂时增加引用计数。


我修改了SmartPhone,以便轻松检查发生了什么。

>>> class SmartPhone(object):
...     def __init__(self, name):
...         self.name = name
...     def __repr__(self):
...         return super(SmartPhone, self).__repr__() + ' name=' + self.name
...     def __del__(self):
...        print "destroyed", self
...
>>> y = SmartPhone('first')
>>> del y # deleted immediately, because only "y" reference it.
destroyed <__main__.SmartPhone object at 0x00000000024FEFD0> name=first
>>> y = SmartPhone('second')
>>> y # at this time, _ reference to second y (y's reference count is now 2)
<__main__.SmartPhone object at 0x00000000024FEFD0> name=second
>>> y
<__main__.SmartPhone object at 0x00000000024FEFD0> name=second
>>> y
<__main__.SmartPhone object at 0x00000000024FEFD0> name=second
>>> del y # not deleted immediately, because _ reference it.
>>> y = SmartPhone('third') # _ still reference the second y, because nothing is printed.
>>> y # second y is deleted, because _ now reference the third y. (no reference to the second y)
destroyed <__main__.SmartPhone object at 0x00000000024FEFD0> name=second
<__main__.SmartPhone object at 0x000000000264A470> name=third

2
太好了!我不知道 _ 存在并会引起这样的麻烦。我正在发布一个我用于实验的不同智能手机,但这是每个人都应该点击的答案。 - tdelaney

6

你一定在同一个解释器会话中重置了y的值,使原始对象的引用计数为0。因此,由于第一个对象没有被引用,它被销毁,但新对象被y所引用。

>>> class SmartPhone:
...   def __del__(self):
...     print 'destroyed'
...
>>> y = SmartPhone()
>>> y
<__main__.SmartPhone instance at 0x00000000021A5608>
>>> y = SmartPhone()
>>> y
destroyed
<__main__.SmartPhone instance at 0x00000000021A5648>

请注意这两个对象的地址是不同的。当第一个实例的__del__0x00000000021A5608处被调用时,会打印出destroyed
在你的例子中,当你明确调用del删除对象引用时,它可能会立即被销毁(如果这是唯一的对象引用且GC立即找到它)。当你执行y = SmartPhone()时,旧对象很可能不会立即被销毁,但当收集器找到它并且看到引用计数为0时,它将被销毁。这通常几乎立即发生,但可能会有延迟。
你的print 'destroyed'可能会立即显示,也可能在你的会话中执行1个或多个其他命令后才显示,但应该很快发生。

在我给出的第二个示例中,为什么“del y”没有立即调用析构函数? - ppone
因为可能在其他地方有一个临时引用对象没有清除,正如@falsetru在他的答案中指出的那样。 - Brian
2
在Python中,不存在“调用del来销毁对象”的概念。因此,您不能使用del立即销毁对象。del的作用是删除一个名称。也就是说,它会删除对对象的一个引用。然后,当垃圾收集器遍历并检查每个对象有多少个引用时,如果一个对象没有引用,那么它才会被销毁。请注意,如果您不显式调用垃圾收集器,它可以在任何时候自由运行,这可能是立即运行,也可能是稍后一些时间。 - John Y
为了澄清,我并没有打算暗示del操作会直接作用于实际对象。 - Brian

0

我运行了相同的代码,但得到了不同的结果:

class SmartPhone:
    def __del__(self):
       print "destroyed"

y = SmartPhone()
del y
print y  #prints destroyed, why is that?

输出:

>>> 
destroyed

Traceback (most recent call last):
  File "C:/Users/Kulanjith/Desktop/rand.py", line 7, in <module>
    print y  #prints destroyed, why is that?
NameError: name 'y' is not defined

实际上,del y确实完成了它的工作,但你却
>>> y = SmartPhone() # create object
>>> del y # delete's the object y, therefore no variable exist after this line executes
destroyed
>>> y = SmartPhone() # again creates a object as a new variable y
>>> y # since no __repr__ methods are define the outcome is normal
<__main__.SmartPhone instance at 0x01A7CBC0>

0

__del__ 不是 C++ 中的析构函数。它是一个方法,保证在对象销毁之前运行,并在对象变得可被垃圾回收后运行。

在 CPython 中,当引用计数达到 0 时会发生这种情况。因此,如果您将一个具有 __del__ 方法的对象赋值给单个变量,那么该对象的方法将在此之后不久被调用。


0

在 @falsetru 的回答上进行扩展,这里有一款智能手机可以轻松查看正在发生的事情。

myid = 0

class SmartPhone(object):
    def __init__(self):
        global myid
        self.myid = myid
        print("init %d" % self.myid)
        myid += 1
    def __del__(self):
        print("delete", self)
    def __repr__(self):
        return "repr %d" % self.myid
    def __str__(self):
        return "str %d" % self.myid

>>> 
>>> y=SmartPhone()
init 0
>>> # _ will hold a ref to y
... 
>>> 
>>> y
repr 0
>>> _
repr 0
>>> # del only decreases the ref count
... 
>>> del y
>>> _
repr 0
>>> # _ still refs 0
... 
>>> y=SmartPhone()
init 1
>>> # but now i reassign _ and 0 goes away
... 
>>> y
delete str 0
repr 1
>>> 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接