Почему слабый не работает на этом связанном методе? - PullRequest
11 голосов
/ 01 марта 2009

У меня есть проект, в котором я пытаюсь использовать слабые ссылки с обратными вызовами, и я не понимаю, что я делаю неправильно. Я создал упрощенный тест, который показывает точное поведение, с которым я запутался.

Почему в этом тесте test_a работает, как и ожидалось, но слабая ссылка для self.MyCallbackB исчезает между инициализацией класса и вызовом test_b? Я думал, что пока экземпляр (a) существует, ссылка на self.MyCallbackB должна существовать, но это не так.

import weakref

class A(object):
    def __init__(self):

        def MyCallbackA():
            print 'MyCallbackA'
        self.MyCallbackA = MyCallbackA

        self._testA = weakref.proxy(self.MyCallbackA)
        self._testB = weakref.proxy(self.MyCallbackB)

    def MyCallbackB(self):
        print 'MyCallbackB'

    def test_a(self):
        self._testA()

    def test_b(self):
        self._testB()

if __name__ == '__main__':
    a = A()    
    a.test_a()
    a.test_b()

Ответы [ 3 ]

12 голосов
/ 01 марта 2009

Вы хотите WeakMethod .

Объяснение, почему ваше решение не работает, можно найти в обсуждении рецепта:

Нормальные списки методов для привязанных методов не совсем работают так, как ожидалось, поскольку связанные методы являются первоклассными объектами; Слабые ссылки на связанные методы являются недействительными по прибытии , если не существует какой-либо другой строгой ссылки на тот же связанный метод.

4 голосов
/ 01 марта 2009

Согласно документации на модуль Weakref:

Далее термин «референт» означает объект, на который ссылаются по слабой ссылке.

Слабая ссылка на объект не достаточно, чтобы сохранить объект: когда единственные оставшиеся ссылки на Референт слабые ссылки, мусор коллекция может уничтожить референт и повторно использовать свою память для что-то еще.

Что происходит с MyCallbackA, так это то, что вы держите ссылку на него в случаях A, благодаря -

self.MyCallbackA = MyCallbackA

Теперь в вашем коде нет ссылки на связанный метод MyCallbackB. Он содержится только в .__ классе __.__ dict__ как несвязанный метод. По сути, связанный метод создается (и возвращается вам), когда вы делаете self.methodName. (AFAIK, связанный метод работает как свойство, используя дескриптор (только для чтения): по крайней мере, для новых классов стилей. Я уверен, что нечто подобное, то есть без дескрипторов, происходит для классов старых стилей. Я оставлю это кто-то более опытный, чтобы проверить утверждение о классах старого стиля.) Итак, self.MyCallbackB умирает, как только создается слабый ответ, потому что на него нет сильной ссылки!

Мои выводы основаны на: -

import weakref

#Trace is called when the object is deleted! - see weakref docs.
def trace(x):
    print "Del MycallbackB"

class A(object):
    def __init__(self):

        def MyCallbackA():
            print 'MyCallbackA'
        self.MyCallbackA = MyCallbackA
        self._testA = weakref.proxy(self.MyCallbackA)
        print "Create MyCallbackB"
        # To fix it, do -
        # self.MyCallbackB = self.MyCallBackB
        # The name on the LHS could be anything, even foo!
        self._testB = weakref.proxy(self.MyCallbackB, trace)
        print "Done playing with MyCallbackB"

    def MyCallbackB(self):
        print 'MyCallbackB'

    def test_a(self):
        self._testA()

    def test_b(self):
        self._testB()

if __name__ == '__main__':
    a = A()  
    #print a.__class__.__dict__["MyCallbackB"] 
    a.test_a()

Выход

Создать MyCallbackB
Del MycallbackB
Закончено играть с MyCallbackB
MyCallbackA

Примечание:
Я попытался проверить это для классов старого стиля. Оказалось, что "print a.test_a .__ get__" выходы -

<method-wrapper '__get__' of instancemethod object at 0xb7d7ffcc>

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

3 голосов
/ 18 марта 2013

Другие ответы относятся к почему в исходном вопросе, но либо не предоставляют обходного пути, ни ссылаются на внешние сайты.

Проработав несколько других постов в StackExchange на эту тему, многие из которых помечены как дубликаты этого вопроса, я наконец пришел к краткому обходному пути. Когда я знаю природу объекта, с которым я имею дело, я использую модуль слабой связи; когда вместо этого я мог иметь дело со связанным методом (как это происходит в моем коде при использовании обратных вызовов событий), я теперь использую следующий класс WeakRef в качестве прямой замены для слабого рефлекса. Я протестировал это с Python 2.4 и выше, включая Python 2.7, но не на Python 3.x.

class WeakRef:

    def __init__ (self, item):

        try:
            self.method   = weakref.ref (item.im_func)
            self.instance = weakref.ref (item.im_self)

        except AttributeError:
            self.reference = weakref.ref (item)

        else:
            self.reference = None


    def __call__ (self):

        if self.reference != None:
            return self.reference ()

        instance = self.instance ()

        if instance == None:
            return None

        method = self.method ()

        return getattr (instance, method.__name__)
...