Слабый ссылочный обратный вызов не вызывается из-за циклических ссылок - PullRequest
3 голосов
/ 19 февраля 2010

Я пытаюсь написать финализатор для классов Python, которые имеют циклические ссылки. Я обнаружил, что обратные вызовы со слабой ссылкой - это способ пойти . К сожалению, кажется, что лямбда, которую я использую в качестве обратного вызова, никогда не вызывается. Например, запустив этот код:

def del_A(name):
    print('An A deleted:' + name)

class A(object):
    def __init__(self, name):
        print('A created')
        self.name = name
        self._wr = weakref.ref(self, lambda wr, n = self.name: del_A(n))

class B(object):
    def __init__(self):
        print('B created')

if __name__ == '__main__':
    a = A('a1')
    b = B()
    a.other = b
    b.other = a

возвращается:

A created
B created

Удаление циклической ссылки приводит к срабатыванию лямбда-обратного вызова (печатается «A A удалено: a1»). Замена лямбда простым вызовом функции тоже работает, но значение параметра фиксируется при инициализации слабой ссылки, а не при вызове обратного вызова:

self._wr = weakref.ref(self, del_A(self.name))
...
a = A('a1')
a.name = 'a2'
b = B()
a.other = b
b.other = a

возвращается:

A created
An A deleted:a1
B created

Есть идеи, почему лямбда-обратный вызов не работает с циклическими ссылками?

Ответы [ 3 ]

2 голосов
/ 19 февраля 2010

Когда вы используете

 self._wr = weakref.ref(self, lambda wr, n = self.name: del_A(n))  

обратный вызов будет вызван только тогда, когда self собирается завершиться.

Причина, по которой обратный вызов не вызывается, заключается в том, что

a = A('a1')
b = B()
a.other = b   # This gives a another attribute; it does not switch `a` away from the original `a`
b.other = a

не приводит к финализации a. Оригинал a все еще существует.

Обратный вызов будет вызван, если вы измените код на

a = A('a1')
b = B()
a = b
b = a

Когда вы используете

self._wr = weakref.ref(self, del_A(self.name))

тогда ваш обратный вызов None. del_A(self.name) это не ссылка на функцию, это сам вызов функции. Таким образом, del_A(self.name) печатает An A deleted:a1 немедленно (до того, как a1 действительно будет завершен) и возвращается со значением None, которое становится обратным вызовом по умолчанию для слабой ссылки.

2 голосов
/ 07 марта 2010

Мне кажется, я наконец нашел причину, по которой обратный вызов не вызывается при наличии слабой ссылки:

Слабые ссылочные обратные вызовы не вызываются, если слабый объект "умирает раньше объекта ссылки "

Похоже, что при удалении циклических ссылок атрибут слабой ссылки класса A удаляется до того, как обратный вызов получит возможность вызова. Одним из решений является добавление финализатора (то есть слабой ссылки и ее обратного вызова) к списку финализаторов. Например:

def del_A(name):
    print('An A deleted:' + name)

class A(object):
    def __init__(self, name, finalizers):
        print('A created')
        self.name = name
        finalizers.append(weakref.ref(self, lambda wr, n = self.name: del_A(n)))

class B(object):
    def __init__(self):
        print('B created')

def do_work(finalizers):
    a = A('a1', finalizers)
    b = B()
    a.other = b
    b.other = a

if __name__ == '__main__':
    finalizers = []
    do_work(finalizers)

напечатает:

A created
B created
An A deleted:a1

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

0 голосов
/ 19 февраля 2010

Циркулярные ссылки очищаются автоматически.Есть несколько исключений, таких как классы, которые определяют метод __del__.

Обычно вам не нужно определять __del__ метод

...