在Python 2.7中异常引发后,对象未释放

时间:2012-01-11 16:01:05

标签: python garbage-collection

我正在使用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 个答案:

答案 0 :(得分:7)

该实例(至少)存储在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对象的信息,其中提到:

  

模块使用回溯对象 - 这是存储在变量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

答案 1 :(得分:3)

经过一番调查后。如果您导入sys并输入

...
a = None

print sys.exc_info()

#sys.exc_clear() # if you add this your A instance will get gc as expected 

gc.collect()
...

你会注意到,即使代码已经在外面,解释器仍然保留对ZeroDivisionError try...catch内部的引用的引用。由于异常保持对引发它们的帧的引用(如果只是为了打印回溯),您的A实例仍然具有非零引用计数。

一旦抛出(并处理)另一个异常,解释器就会删除对第一个异常的引用,并收集异常和连接到它的对象。

答案 2 :(得分:1)

在代码中添加一些调试行:

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来解决性能敏感问题是一个错误的想法 - 二进制文件本身很大,并且你将永远不需要包中的大量膨胀。为什么不试着把手转到一点C?

答案 3 :(得分:1)

如果对象曾经是一个在except子句中捕获表达式的函数中的局部变量,则很有可能对该对象的引用仍然存在于堆栈跟踪中包含的该函数的堆栈帧中。通常,调用sys.exc_clear()将通过清除最后记录的异常来处理这个问题。

http://docs.python.org/faq/programming.html#id55