GC не удаляет циркулярные ссылки в WeakKeyDictionaries? - PullRequest
6 голосов
/ 02 июня 2011

У меня есть ситуация, в которой я хотел бы сохранить отображение от одного объекта к другому, пока существует первый объект. Моей первой мыслью было использовать WeakKeyDictionary.

import weakref
import gc

class M:
    pass

w = weakref.WeakKeyDictionary()
m = M()
w[m] = some_other_object
del m
gc.collect()
print w.keys()

В большинстве случаев работает нормально. Однако в случае, когда some_other_object является (или имеет ссылку на) m, экземпляр M не будет собирать мусор. (Чтобы увидеть пример, замените some_other_object на m)

Конечно, если бы я сохранил отображение на самом объекте, при удалении он был бы собран мусором:

import weakref
import gc

class M:
    pass

m = M()
m.circular_reference = m
r = weakref.ref(m)
del m
gc.collect()
print r

Могу ли я достичь результатов второго примера, используя модуль слабой связи (т.е. без мутирования m)?

Другими словами, могу ли я использовать модуль слабой связи для сопоставления объекта с самим собой (или с другим объектом, который имеет на него сильную ссылку) и сохранять объект в памяти только до тех пор, пока есть другие ссылки на него? *

Ответы [ 2 ]

2 голосов
/ 05 июля 2011

В этих примерах у вас нет круговых ссылок.Ваш круг выглядит так:

WeakKeyDict -> value -> key -> ...

Таким образом, dict сохраняет значение, которое, в свою очередь, поддерживает ключ в действии, и с помощью отдельного механизма, который не включает в себя строгую ссылку , ключ дает указание диктату сохранить значение.Так как здесь нет правильного круга ссылок, GC никогда не обнаруживает никаких проблем.(Тем не менее, ваш второй пример содержит круговую ссылку, поэтому он ведет себя как первый)

Я подозреваю, что единственным решением вашей проблемы является обеспечение того, чтобы значения dictникогда не имейте сильных ссылок (прямых или косвенных) ни на один из ключей в диктате.Это похоже на то, что предложил srgerg, но вместо того, чтобы делать слабые ссылки на все значения, вы действительно хотите, чтобы ссылки на ключи были слабыми.Например:

import weakref
import gc

class M:
    pass

w = weakref.WeakKeyDictionary()
key = M()
val = M()
val.keyRef = weakref.ref(val)
w[key] = val
print len(w)   ## prints 1
del key
gc.collect()
print len(w)   ## prints 0

Таким образом, всегда есть сильные ссылки на значения, но вы тщательно контролируете ссылки на ключи, чтобы определить, что будет удалено из dict.В зависимости от сложности вашей программы это может занять много времени, поскольку вам нужно вручную отследить все ссылки на ключи.

Но, возможно, мы сможем предложить более простое решение, если вы расскажете нам больше о конкретной проблеме ..

1 голос
/ 02 июня 2011

Работа с циклическими ссылками, как это, вероятно, поможет вам, но, без сомнения, я все равно попытаюсь ответить на ваш вопрос.

Словарь Python хранит ссылки как на его ключи, так и на его значения.A WeakKeyDictionary хранит слабые ссылки на его ключи, но strong ссылки на его значения.Итак, в вашем первом примере, если вы установите w[m] = m, словарь w имеет слабую ссылку на m в качестве ключа и сильную ссылку на m в качестве значения.

Модуль Python слабой ссылки такжеимеет WeakValueDictionary, который имеет сильные ссылки на свои ключи и слабые ссылки на его значения, но это не решит вашу проблему.

Что вам действительно нужно, так это словарь, который имеет слабые ссылки как на свои ключи, так и на значения.Следующий код явно делает каждое значение в словаре слабой ссылкой:

import weakref
import gc

class M():
  pass

class Other():
  pass

m = M()
w = weakref.WeakKeyDictionary()
w[m] = weakref.ref(m)
print len(w.keys()) # prints 1
del m
print len(w.keys()) # prints 0

m = M()
o = Other()
o.strong_ref = m
w[m] = weakref.ref(o)
print len(w.keys()) # prints 1
del m
print len(w.keys()) # prints 1
del o
print len(w.keys()) # prints 0

Вы можете сделать значение слабой ссылкой автоматически, подклассифицировав WeakKeyDictionary.Обратите внимание, однако, что это не ведет себя как настоящий WeakValueDictionary, потому что он не имеет обратного вызова для уведомления словаря, когда значение было удалено.

class MyWeakKeyValueDictionary(weakref.WeakKeyDictionary):
    def __init__(self):
        weakref.WeakKeyDictionary.__init__(self)

    def __setitem__(self, key, value):
        weakref.WeakKeyDictionary.__setitem__(self, key, weakref.ref(value))

w2 = MyWeakKeyValueDictionary()
m = M()
w2[m] = m
print len(w2.keys()) # prints 1
del m
print len(w2.keys()) # prints 0

m = M()
o = Other()
o.strong_ref = m
w2[m] = o
print len(w2.keys()) # prints 1
del m
print len(w2.keys()) # prints 1
del o
print len(w2.keys()) # prints 0

В конце концов,однако, я боюсь, что попытка справиться с циклическими ссылками может вызвать больше проблем, чем оно того стоит.

...