2009-03-01 4 views
9

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

Почему в этом тесте test_a работает так, как ожидалось, но weakref для 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() 

ответ

10

Вы хотите получить WeakMethod.

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

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

4

В соответствии с документацией для модуля Weakref:

В дальнейшем, термине референт означает объект, на который ссылается слабой ссылкой.

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

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

self.MyCallbackA = MyCallbackA 

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

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

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

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

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

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

3

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

После обработки нескольких других сообщений на StackExchange по этой теме, многие из которых отмечены как дубликаты этого вопроса, я, наконец, пришел к краткому обходному пути. Когда я знаю природу объекта, с которым имею дело, я использую модуль weakref; когда я могу вместо этого обращаться со связанным методом (как это происходит в моем коде при использовании обратных вызовов событий), теперь я использую следующий класс WeakRef как прямую замену для weakref.ref(). Я тестировал это с помощью 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__) 
+1

Для питона 3 заменить '' item.im_func' с товаром .__ func__' и '' item.im_self' с пунктом .__ self__' – lou

+0

Может нужно '' Защита __eq __ (я, другой): вернуться другое() == self() ''? Это лучший образец сравнения? (и, вероятно, '' __ne__'' как обратное) – Rafe

Смежные вопросы