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

发布于 2024-12-26 12:04:53 字数 833 浏览 3 评论 0原文

我正在使用 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实例存储在哪里?

多谢 亚历克斯

I am using Python 2.7 and trying to have a clean memory (as I am writing a small server). I am facing an issue where the object of the last raise are still kept in the garbage collector (and then __ del __ is not called after the first try/except).

Here is a small example:

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)

and this returns:

1. Nbr of instance of A in gc : 
1
2. Nbr of instance of A in gc : 
0

while I was waiting to have 0 for both. Where Is the A instance stored ?

Thanks a lot
Alex

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

心碎无痕… 2025-01-02 12:04:53

该实例(至少)存储在 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'>])]

所以我们看到框架至少由回溯保存对象。我们可以在 tracebacktraceback 对象的信息code> 模块,其中提到:

该模块使用回溯对象 - 这是存储在变量 sys.exc_traceback(已弃用)和 sys.last_traceback 中的对象类型,并作为第三项返回来自sys.exc_info()

这意味着这些系统变量可能是使帧保持活动状态的变量。事实上,如果我们调用 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

That instance is stored (at least) in the interrupted frame of the raiser function, as we can check using 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)]

This prints:

<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 : 
[]

Note the last object is the same as the frame of raiser. This also means you'll get the same result too if you just write

try:
    A().raiser()
except:
    pass

We could do the same trick again to see what is holding the frame object:

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)]

The result is

[(['<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'>])]

So we see that the frame is at least held by a traceback object. We can find info about the traceback object in the traceback module, which mentions:

The module uses traceback objects — this is the object type that is stored in the variables sys.exc_traceback (deprecated) and sys.last_traceback and returned as the third item from sys.exc_info().

Which means these sys variables may be the one that is holding the frame alive. Indeed, if we call sys.exc_clear() to clear the exception info, the instance will be deallocated:

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
少钕鈤記 2025-01-02 12:04:53

经过一番调查。如果导入 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 实例仍然具有非零引用计数。

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

After a little investigation. If you import sys and put

...
a = None

print sys.exc_info()

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

gc.collect()
...

you'll notice that the interpreter still holds a reference to the ZeroDivisionError trown inside try...catch even though the code is already outside. Since exception hold references to frames where they are thrown (if only to print traceback), your A instance still has non-zero refcount.

Once another exception is thrown (and handled) interpreter drops the reference to the first one and collects both the exception and objects connected to it.

梦毁影碎の 2025-01-02 12:04:53

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

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)

returns

1. Tracking a: True
2. Tracking a: False

这表明在 a = None 之后该对象没有被跟踪,因此当需要空间时它将从堆中释放。所以a没有被存储,但是Python认为没有必要完全取消引用它(忽略它可能比从堆栈中清除它更便宜) 。

然而,使用 python 来解决性能敏感问题是一个的主意 - 二进制文件本身很大,并且有很多来自你永远不需要的包的膨胀。为什么不试试把你的手变成小C呢?

Adding some debug lines to your code:

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)

returns

1. Tracking a: True
2. Tracking a: False

This shows that the object is not being tracked after the a = None so it will be released from the heap when the space is needed. So a is not stored, but Python doesn't see the need to completely de-reference it (it is probably cheaper to ignore it than to purge it from the stack).

However, using python for performance sensitive problems is a bad idea - the binary itself is large, and there is a lot of bloat from packages you will never need. Why not try turning your hand to a little C?

千柳 2025-01-02 12:04:53

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

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

If the object has ever been a local variable in a function that caught an expression in an except clause, chances are that a reference to the object still exists in that function’s stack frame as contained in the stack trace. Normally, calling sys.exc_clear() will take care of this by clearing the last recorded exception.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文