Python 2.7中异常抛出后对象未释放
我正在使用Python 2.7,想要保持内存的干净(因为我在写一个小服务器)。我遇到了一个问题,就是最后一次抛出的异常对象仍然被垃圾回收器保留着(所以在第一次尝试/异常处理后,__del__方法没有被调用)。
这里有一个小例子:
import gc
class A(object):
def raiser(self):
0/0 # will raise an exception
a = A()
try:
a.raiser()
except:
pass
a = None # should release the object from memory
gc.collect() # just to be sure, but doesn't do anything
print '1. Nbr of instance of A in gc : '
print len([o for o in gc.get_objects() if isinstance(o, A)]) # get 1 but expected 1
try:
0/0
except:
pass
print '2. Nbr of instance of A in gc : '
print len([o for o in gc.get_objects() if isinstance(o, A)]) # get 0 (finally)
这个例子返回了:
1. Nbr of instance of A in gc :
1
2. Nbr of instance of A in gc :
0
而我原本希望这两个值都是0。那这个A实例到底存在哪里呢?
非常感谢,
亚历克斯
4 个回答
在你的代码中添加一些调试信息:
import gc
gc.set_debug(gc.DEBUG_STATS)
class A(object):
def raiser(self):
0/0 # will raise an exception
a = A()
print 'Tracking a:', gc.is_tracked(a)
try:
a.raiser()
except:
pass
a = None # should release the object from memory
print 'Tracking a:', gc.is_tracked(a)
返回结果是
1. Tracking a: True
2. Tracking a: False
这说明在执行 a = None
之后,这个对象就不再被追踪了,所以当需要空间时,它会从内存中释放掉。因此,a
是 没有 被存储的,但Python并不觉得有必要完全去掉它(可能忽略它比从栈中清除它更省事)。
不过,用Python来解决对性能要求很高的问题其实是个 坏 主意——因为Python的二进制文件很大,而且有很多你根本用不上的包会让它变得臃肿。为什么不试试学一点C语言呢?
经过一点调查,如果你导入了 sys
模块并放入
...
a = None
print sys.exc_info()
#sys.exc_clear() # if you add this your A instance will get gc as expected
gc.collect()
...
你会发现,解释器仍然保持着对在 try...catch
中抛出的 ZeroDivisionError
的引用,即使代码已经执行到外面了。因为异常会保持对它们被抛出的地方的引用(至少是为了打印错误追踪信息),所以你的 A 实例仍然有非零的引用计数。
一旦抛出另一个异常(并被处理),解释器就会放弃对第一个异常的引用,并清理掉这个异常以及与之相关的对象。
这个实例至少存储在raiser
函数的中断帧中,我们可以通过gc.get_referrers
来检查:
import gc
import inspect
class A(object):
def raiser(self):
print inspect.currentframe()
0/0
a = A()
try:
a.raiser()
except:
pass
a = None # should release the object from memory
gc.collect() # just to be sure, but doesn't do anything
print 'b. Nbr of instance of A in gc : '
print [map(lambda s: str(s)[:64], gc.get_referrers(o)) for o in gc.get_objects() if isinstance(o, A)]
try:
0/0
except:
pass
print '---'
print 'c. Nbr of instance of A in gc : '
print [map(lambda s: str(s)[:64], gc.get_referrers(o)) for o in gc.get_objects() if isinstance(o, A)]
这会打印出:
<frame object at 0x239fa70>
---
b. Nbr of instance of A in gc :
[["[[], ('Return a new Arguments object replacing specified fields ",
"{'A': <class '__main__.A'>, 'a': None, '__builtins__': <module '",
'<frame object at 0x239fa70>']]
---
c. Nbr of instance of A in gc :
[]
注意最后一个对象和raiser
的帧是一样的。这也意味着如果你直接写的话,结果也是一样的:
try:
A().raiser()
except:
pass
我们可以再用同样的方法来看看是什么在持有这个帧对象:
class A(object):
def raiser(self):
0/0
try:
A().raiser()
except:
pass
print [(map(lambda x: str(x)[:64], gc.get_referrers(o)), # Print the referrers
map(type, gc.get_referents(o))) # Check if it's the frame holding an A
for o in gc.get_objects()
if inspect.isframe(o)]
结果是:
[(['<traceback object at 0x7f07774a3bd8>',
'[<function getblock at 0x7f0777462cf8>, <function getsourcelines',
"{'A': <class '__main__.A'>, '__builtins__': <module '__builtin__"
], [<type 'frame'>, <type 'code'>, <type 'dict'>, <type 'dict'>,
<class '__main__.A'>]),
(['<frame object at 0xe3d3c0>',
'<traceback object at 0x7f07774a3f38>',
'[<function getblock at 0x7f0777462cf8>, <function getsourcelines',
"{'A': <class '__main__.A'>, '__builtins__': <module '__builtin__"
], [<type 'code'>, <type 'dict'>, <type 'dict'>, <type 'dict'>,
<type 'NoneType'>])]
所以我们看到这个帧至少是被一个traceback
对象持有的。我们可以在traceback
模块中找到关于traceback
对象的信息,里面提到:
这个模块使用traceback对象——这就是存储在
sys.exc_traceback
(已弃用)和sys.last_traceback
变量中的对象类型,并作为sys.exc_info()
的第三个返回值。
这意味着这些sys变量可能是保持帧存活的原因。实际上,如果我们调用sys.exc_clear()
来清除异常信息,这个实例就会被释放:
import gc
import sys
class A(object):
def raiser(self):
0/0
try:
A().raiser()
except:
pass
print len([o for o in gc.get_objects() if isinstance(o, A)]) # prints 1
sys.exc_clear()
print len([o for o in gc.get_objects() if isinstance(o, A)]) # prints 0