В 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(), которые можно использовать, когда вы хотите помещать слабые ссылки в более сложные места, такие как ключи словаря. Эти типы не требуют ручной обрезки.

Ответ 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". Поэтому, если вы создаете несколько слабых сторон для одного и того же объекта с одной и той же функцией обратного вызова, он будет называться точным в то же время, что и число слабых ссылок.