В Python: как удалить объект из списка, если он указан только в этом списке?
Я хочу отслеживать объекты определенного типа, которые в настоящее время используются. Например: отслеживать все экземпляры класса или всех классов, созданных метаклассом.
Легко отслеживать такие случаи:
class A():
instances = []
def __init__(self):
self.instances.append(self)
Но если экземпляр не ссылается нигде вне этого списка, он больше не понадобится, и я не хочу обрабатывать этот экземпляр в потенциально длительном цикле.
Я попытался удалить объекты, которые упоминаются только в списке, используя sys.getrefcount.
for i in A.instances:
if sys.getrefcount(i) <=3: # in the list, in the loop and in getrefcount
# collect and remove after the loop
Проблема заключается в том, что счетчик ссылок очень неясен.
Открытие новой оболочки и создание фиктивного класса без возврата контента 5 для
sys.getrefcount(DummyClass)
Другая идея - скопировать объекты, а затем удалить список и проверить, какие объекты были запланированы для сбора мусора, и на последнем этапе удалить эти объекты. Что-то вроде:
Copy = copy(A.instances)
del A.instances
A.instances = [i for i in Copy if not copy_of_i_is_in_GC(i)]
Объекты не нужно удалять сразу, когда счетчик ссылок равен 0. Я просто не хочу тратить слишком много ресурсов на объекты, которые больше не используются.
Ответы
Ответ 1
Этот ответ такой же, как у Кевина, но я работал над реализацией примера со слабыми ссылками и размещаю его здесь. Использование слабых ссылок решает проблему, когда объект ссылается на список self.instance
, поэтому он никогда не будет удален.
Одна из вещей, связанных с созданием слабой ссылки для объекта, заключается в том, что вы можете включить обратный вызов, когда объект будет удален. Есть такие проблемы, как обратный вызов не происходит, когда программа выходит... но это может быть то, что вы хотите в любом случае.
import threading
import weakref
class A(object):
instances = []
lock = threading.RLock()
@classmethod
def _cleanup_ref(cls, ref):
print('cleanup') # debug
with cls.lock:
try:
cls.instances.remove(ref)
except ValueError:
pass
def __init__(self):
with self.lock:
self.instances.append(weakref.ref(self, self._cleanup_ref))
# test
test = [A() for _ in range(3)]
for i in range(3,-1,-1):
assert len(A.instances) == i
if test:
test.pop()
print("see if 3 are removed at exit")
test = [A() for _ in range(3)]
Ответ 2
Стандартный способ решения этой проблемы - слабые ссылки. Основная идея заключается в том, что вы храните список слабых ссылок на объекты вместо самих объектов и периодически обрезаете мертвые слабые ссылки из списка.
Для словарей и наборов есть еще несколько абстрактных типов, таких как weakref.WeakKeyDictionary()
, которые можно использовать, когда вы хотите помещать слабые ссылки в более сложные места, такие как ключи словаря. Эти типы не требуют ручной обрезки.
Ответ 3
Попробуйте gc.get_referrers(obj)
. Документация модуля gc
len(gc.get_referrers(my_obj))
Ответ 4
Благодаря @Barmar за указание использовать weakref. Мы можем объединить его с методом __del__
для реализации списка экземпляров самоконтроля класса. Таким образом, сообщение class A
в сообщении OP может быть расширено как:
from weakref import ref
class A():
instances = []
def __init__(self):
self.instances.append(ref(self))
@staticmethod
def __del__():
if A:
A.instances = [i for i in A.instances if not i() is None]
Тестирование
#python2.7
print dict((len(A.instances), A()) for i in range(5)).keys() # 0,1,2,3,4
print len(A.instances) # 0
Деструктор __del__
может быть объявлен как статический метод или связанный с объектом метод, например def __del__(self):
, хотя он не документирован. Последний может остановить уничтожение объекта, создав еще одну ссылку на него. Здесь я использую статический, потому что нет необходимости в другой ссылке на объект умирающего. Вышеприведенный код проверяется как на Python 2.7, так и на 3.3.
Обратный вызов weakref.ref
ведет себя аналогично __del__
, за исключением того, что он привязан к объекту "weakref". Поэтому, если вы создаете несколько слабых сторон для одного и того же объекта с одной и той же функцией обратного вызова, он будет называться точным в то же время, что и число слабых ссылок.