커넥터에 대한 참조를 유지하지 않고 Python에서 Gobject 신호에 연결하는 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/1364923

  •  21-09-2019
  •  | 
  •  

문제

문제는 기본적으로 Python의 Gobject 및 GTK 바인딩에서입니다. 우리가 구성 할 때 신호에 바인딩하는 클래스가 있다고 가정합니다.

class ClipboardMonitor (object):
  def __init__(self):
    clip = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
    clip.connect("owner-change", self._clipboard_changed)

이제 문제는 이제 ClipboardMonitor의 인스턴스는 죽지 않을 것입니다. GTK 클립 보드는 응용 프로그램 전체 객체이며 연결하면 콜백을 사용하므로 객체에 대한 참조를 유지합니다. self._clipboard_changed.

약한 참조 (약점 모듈)를 사용 하여이 문제를 해결하는 방법에 대해 토론하고 있지만 아직 계획을 세우지 않았습니다. 누구나 신호 등록에 콜백을 전달하는 방법을 알고 있으며 약한 기준처럼 작동하도록합니다 (클립 보드 모니터 인스턴스가 범위를 벗어난 경우 신호 콜백이 호출되면 NO-OP 여야합니다).

덧셈: gobject 또는 gtk+와 독립적으로 표현

약점 시맨틱과 함께 불투명 객체에 콜백 방법을 어떻게 제공합니까? 연결 객체가 범위를 벗어나면 삭제해야하며 콜백은 NO-OP 역할을해야합니다. Connectee는 커넥터에 대한 참조를 보유해서는 안됩니다.

명확히하려면 : "Destructor/Finalizer"방법을 호출하지 않아도됩니다.

도움이 되었습니까?

해결책

표준 방법은 신호를 분리하는 것입니다. 그러나 이것은 당신의 객체를 유지하는 코드에 의해 명시 적으로 불리는 소멸자와 같은 방법이 필요합니다. 그렇지 않으면 원형 의존성을 얻을 수 있기 때문에 이것은 필요합니다.

class ClipboardMonitor(object):
    [...]

    def __init__(self):
        self.clip = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
        self.signal_id = self.clip.connect("owner-change", self._clipboard_changed)

    def close(self):
        self.clip.disconnect(self.signal_id)

지적했듯이, 설명 파괴를 피하려면 약점이 필요합니다. 나는 약한 콜백 공장을 쓸 것입니다.

import weakref

class CallbackWrapper(object):
    def __init__(self, sender, callback):
        self.weak_obj = weakref.ref(callback.im_self)
        self.weak_fun = weakref.ref(callback.im_func)
        self.sender = sender
        self.handle = None

    def __call__(self, *things):
        obj = self.weak_obj()
        fun = self.weak_fun()
        if obj is not None and fun is not None:
            return fun(obj, *things)
        elif self.handle is not None:
            self.sender.disconnect(self.handle)
            self.handle = None
            self.sender = None

def weak_connect(sender, signal, callback):
    wrapper = CallbackWrapper(sender, callback)
    wrapper.handle = sender.connect(signal, wrapper)
    return wrapper

(이것은 개념 증명 코드이며 저에게 효과적입니다. 아마도이 부분을 필요에 맞게 조정해야 할 것입니다). 몇 가지 메모 :

  • 콜백 객체 및 기능 분리 텔리를 저장하고 있습니다. 바운드 방법은 매우 일시적인 객체이기 때문에 단순히 경계 방법의 약점을 만들 수는 없습니다. 실제로 weakref.ref(obj.method) 약점을 만든 후 바운드 메소드 객체를 즉시 파괴합니다. 약점을 함수에 저장하는 데 필요한지 여부를 확인하지 않았습니다 ... 코드가 정적 인 경우 피할 수있을 것입니다.
  • 객체 래퍼는 약한 기준이 존재하지 않는다는 것을 알았을 때 신호 전송자에서 자체적으로 제거됩니다. 이것은 또한 콜백 자퍼와 신호 전송자 개체 사이의 원형 의존성을 파괴하는 데 필요합니다.

다른 팁

(이 답변은 내 진보를 추적합니다)

이 두 번째 버전도 연결이 끊어집니다. gobjects에 대한 편의 기능이 있지만 실제로 D-BUS 신호 콜백 및 gobject 콜백의 경우보다 일반적인 경우이 클래스가 필요합니다.

어쨌든, 무엇을 부를 수 있는가 WeakCallback 구현 스타일? 그것은 약한 콜백의 매우 깨끗한 캡슐화이지만 Gobject/dbus 전문화가 눈에 띄게 다루어졌습니다. 이 두 경우에 대해 두 개의 서브 클래스를 쓰는 것입니다.

import weakref

class WeakCallback (object):
    """A Weak Callback object that will keep a reference to
    the connecting object with weakref semantics.

    This allows to connect to gobject signals without it keeping
    the connecting object alive forever.

    Will use @gobject_token or @dbus_token if set as follows:
        sender.disconnect(gobject_token)
        dbus_token.remove()
    """
    def __init__(self, obj, attr):
        """Create a new Weak Callback calling the method @obj.@attr"""
        self.wref = weakref.ref(obj)
        self.callback_attr = attr
        self.gobject_token = None
        self.dbus_token = None

    def __call__(self, *args, **kwargs):
        obj = self.wref()
        if obj:
            attr = getattr(obj, self.callback_attr)
            attr(*args, **kwargs)
        elif self.gobject_token:
            sender = args[0]
            sender.disconnect(self.gobject_token)
            self.gobject_token = None
        elif self.dbus_token:
            self.dbus_token.remove()
            self.dbus_token = None

def gobject_connect_weakly(sender, signal, connector, attr, *user_args):
    """Connect weakly to GObject @sender's @signal,
    with a callback in @connector named @attr.
    """
    wc = WeakCallback(connector, attr)
    wc.gobject_token = sender.connect(signal, wc, *user_args)

실제로 아직 시도하지는 않았지만 :

class WeakCallback(object):
    """
    Used to wrap bound methods without keeping a ref to the underlying object.
    You can also pass in user_data and user_kwargs in the same way as with
    rpartial. Note that refs will be kept to everything you pass in other than
    the callback, which will have a weakref kept to it.
    """
    def __init__(self, callback, *user_data, **user_kwargs):
        self.im_self = weakref.proxy(callback.im_self, self._invalidated)
        self.im_func = weakref.proxy(callback.im_func)
        self.user_data = user_data
        self.user_kwargs = user_kwargs

    def __call__(self, *args, **kwargs):
        kwargs.update(self.user_kwargs)
        args += self.user_data
        self.im_func(self.im_self, *args, **kwargs)

    def _invalidated(self, im_self):
        """Called by the weakref.proxy object."""
        cb = getattr(self, 'cancel_callback', None)
        if cb is not None:
            cb()

    def add_cancel_function(self, cancel_callback):
        """
        A ref will be kept to cancel_callback. It will be called back without
        any args when the underlying object dies.
        You can wrap it in WeakCallback if you want, but that's a bit too
        self-referrential for me to do by default. Also, that would stop you
        being able to use a lambda as the cancel_callback.
        """
        self.cancel_callback = cancel_callback

def weak_connect(sender, signal, callback):
    """
    API-compatible with the function described in
    http://stackoverflow.com/questions/1364923/. Mostly used as an example.
    """
    cb = WeakCallback(callback)
    handle = sender.connect(signal, cb)
    cb.add_cancel_function(WeakCallback(sender.disconnect, handle))
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top