كيفية الاتصال بإشارة GoBject في Python ، دون الحفاظ على إشارة إلى الموصل؟

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

  •  21-09-2019
  •  | 
  •  

سؤال

المشكلة هي هذا بشكل أساسي ، في روابط GoBject و GTK الخاصة بيثون. افترض أن لدينا فئة ترتبط بإشارة عند بنائها:

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

المشكلة الآن ، لن يموت أي مثيل من الحافظة. حافظة GTK هي كائن على نطاق التطبيق ، والاتصال به يحتفظ بالإشارة إلى الكائن ، نظرًا لأننا نستخدم رد الاتصال self._clipboard_changed.

أنا أناقش كيفية العمل حول هذا باستخدام المراجع الضعيفة (الوحدة النمطية الضعيفة) ، لكنني لم أتوصل إلى خطة. أي شخص لديه فكرة عن كيفية نقل رد الاتصال إلى تسجيل الإشارة ، ويكون يتصرف مثل مرجع ضعيف (إذا تم استدعاء اتصال الإشارة عندما يكون مثيل الحافظة خارج نطاقه ، فيجب أن يكون عدم وجود OP).

إضافة: صاغها بشكل مستقل عن goBject أو GTK+:

كيف يمكنك تقديم طريقة رد اتصال لكائن غير شفاف ، مع دلالات الضعف؟ إذا خرج كائن التوصيل عن النطاق ، فيجب حذفه وينبغي أن يكون رد الاتصال بمثابة عدم وجود 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) سوف يدمر كائن الطريقة المربوطة على الفور بعد إنشاء ضعيف. لم أتحقق مما إذا كانت هناك حاجة لتخزين نقطة ضعف في الوظيفة أيضًا ... أعتقد أنه إذا كان الكود ثابتًا ، فربما يمكنك تجنب ذلك.
  • سيقوم غلاف الكائن بإزالة نفسه من مرسل الإشارة عندما يلاحظ أن المرجع الضعيف لم يعد موجودًا. هذا ضروري أيضًا لتدمير الاعتماد الدائري بين Conflwrapper وكائن مرسل الإشارة.

نصائح أخرى

(هذه الإجابة تتعقب تقدمي)

هذا الإصدار الثاني سوف ينفصل أيضًا ؛ لديّ وظيفة راحة لـ 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