在Python中不应调用__del__方法时却被调用
我刚开始学习Python,正在看Swaroop CH的《A Byte of Python》里的例子。最近我遇到了一个关于__del__
方法的奇怪情况。
简单来说,如果我在Python 2.6.2中运行以下脚本:
class Person4:
'''Represents a person'''
population = 0
def __init__(self, name):
'''Initialize the person's data'''
self.name = name
print 'Initializing %s'% self.name
#When the person is created they increase the population
Person4.population += 1
def __del__(self):
'''I am dying'''
print '%s says bye' % self.name
Person4.population -= 1
if Person4.population == 0:
print 'I am the last one'
else:
print 'There are still %d left' % Person4.population
swaroop = Person4('Swaroop')
kaleem = Person4('Kalem')
在Python控制台(或者Spyder的交互式控制台)中,我看到的结果是:
execfile(u'C:\1_eric\Python\test1.py')
正在初始化Swaroop
正在初始化Kalemexecfile(u'C:\1_eric\Python\test1.py')
正在初始化Swaroop
Swaroop说再见
我是最后一个
正在初始化Kalem
Kalem说再见
我是最后一个
为什么在第二次运行时,__del__
方法会在__init__
方法之后立刻被调用呢?
我猜是因为使用了相同的实例名称('swaroop'和'kaleem'),所以它释放了原来的实例并进行了垃圾回收。但这似乎搞乱了当前的实例计数。
这到底是怎么回事呢?
有什么好的方法可以避免这种困惑吗?
是不是应该避免使用__del__
?
在重用实例名称之前检查一下现有的实例名称?
...
谢谢,
Eric
2 个回答
一般建议:在Python中不要使用__del__。因为它可能会以多种方式破坏垃圾回收,特别是在对象之间存在循环引用的情况下。
在你的例子中,使用execfile()会有很多问题——这并不是一个好的做法——而且重新定义全局变量也会引发问题。顺便说一下,如果你真的需要创建一个伪析构函数(也就是在对象被垃圾回收时调用的代码),可以写一个所谓的“终结器”函数(这并不是真正的析构函数),然后通过weakref.ref的回调来调用它。这个函数当然不能是实例方法,而且要记住,lambda实际上会创建一个闭包,所以一定要确保在回调中不要泄露对self的引用!如果你需要从被销毁的实例中获取数据,可以使用函数的默认参数方法,只要确保绝对不要在lambda中引用'self',否则就会失效。
from weakref import ref
from time import sleep
class Person4:
'''Represents a person'''
population = 0
def __init__(self, name):
'''Initialize the person's data'''
self.name = name
print 'Initializing %s'% self.name
#When the person is created they increase the population
Person4.population += 1
self._wr = ref(self, lambda wr, name=self.name: Person4_finalizer(name))
def Person4_finalizer(name):
'''I am dying'''
print '%s says bye' % name
Person4.population -= 1
if Person4.population == 0:
print 'I am the last one'
else:
print 'There are still %d left' % Person4.population
p1 = Person4("one")
p2 = Person4("two")
p3 = Person4("three")
del p2
del p3
sleep(5)
输出(这里加了sleep是为了帮助观察发生了什么):
Initializing one
Initializing two
Initializing three
two says bye
There are still 2 left
three says bye
There are still 1 left
one says bye
I am the last one
这里有几个事情需要说明。当你创建 Person4
类的实例时,它会把 population
这个类变量初始化为0。从你在交互式控制台的操作来看,你似乎多次运行了你的 "test1.py" 文件。第二次运行时,Person4
类又被声明了一次,这样它在技术上就和第一次的不一样了(尽管名字是一样的)。这意味着它有自己独立的 population
计数。
现在,swaroop
和 kaleem
是 全局 变量,它们在你两个 "test1.py" 的实例之间是共享的。Python 在内部使用引用计数来进行大部分自动垃圾回收,所以第一个 Person4
类的原始实例在第二次给 swaroop
赋值之前是不会被释放的。给 swaroop
赋值会减少第一个实例的引用计数,这时会调用 __del__
,因为引用计数变成了零。但是因为你在 __del__()
里面通过名字引用了 Person4
,当 之前的 实例消失时,它会减少 新的 Person4.population
的计数,而不是旧的 Person4
的计数。
希望这些解释能让你明白。我能理解这对学习Python的人来说可能会有些困惑。你在使用类变量的同时又用 execfile()
重新定义 Person4
类,这让事情变得更加复杂。说实话,我写了很多Python代码,但我觉得我从来没有需要用到过 __del__
这个特殊方法。