Объект не освобожден после возникновения исключения в Python 2.7 - PullRequest
4 голосов
/ 11 января 2012

Я использую 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?

Большое спасибо Alex

Ответы [ 4 ]

7 голосов
/ 11 января 2012

Этот экземпляр сохраняется (по крайней мере) в прерванном кадре функции 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
3 голосов
/ 11 января 2012

После небольшого расследования. Если вы импортируете 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 по-прежнему имеет ненулевой счетчик ссылок.

Когда выдается и обрабатывается другое исключение, интерпретатор сбрасывает ссылку на первое и собирает как исключение, так и связанные с ним объекты.

1 голос
/ 19 января 2012

Если объект когда-либо был локальной переменной в функции, которая перехватила выражение в предложении кроме, есть вероятность, что ссылка на объект все еще существует в кадре стека этой функции, как указано в трассировке стека.Обычно вызов sys.exc_clear () позаботится об этом, очистив последнее записанное исключение.

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

1 голос
/ 11 января 2012

Добавление некоторых строк отладки в ваш код:

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?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...