متى تستخدم مراجع ضعيفة في بيثون؟
-
19-09-2019 - |
سؤال
هل يمكن لأي شخص أن يفسر استخدام المراجع الضعيفة؟
ال توثيق لا يفسر ذلك على وجه التحديد، كما يقول فقط أن GC يمكن أن يدمر الكائن المرتبط عبر مرجع ضعيف في أي وقت. ثم ما هي نقطة وجود كائن يمكن أن تختفي في أي وقت؟ ماذا لو كنت بحاجة إلى استخدامه مباشرة بعد اختفائه؟
هل يمكن أن توضح لهم بعض الأمثلة الجيدة؟
شكرا
المحلول
الاستخدام النموذجي للمراجع الضعيفة هو إذا كان لديك إشارة إلى B و B مرجع إلى A. دون جامع القمامة مناسبة للكشف عن الدورة، لن تحصل هذه الكائنتين على GC'D أبدا حتى لو لم تكن هناك مراجع إما من "الخارج". ومع ذلك، إذا كانت إحدى المراجع "ضعيفة"، فستحصل الكائنات بشكل صحيح على GC'D.
ومع ذلك، بايثون هل لديك مجمع القمامة للكشف عن الدورة (منذ 2.0!)، لذلك لا يعتبر :)
استخدام آخر للمراجع الضعيفة هو المخابئ. لقد ذكر في weakref
توثيق:
الاستخدام الأساسي للمراجع الضعيفة هو تنفيذ مخطط التخزين المؤقت أو التعيينات التي تحمل كائنات كبيرة، حيث ترغب في أن يتم إظهار كائن كبير على قيد الحياة فقط لأنه يظهر في ذاكرة التخزين المؤقت أو رسم خرائط.
إذا قررت شركة GC تدمير إحدى هذه الكائنات، وتحتاج إليها، فيمكنك إعادة حساب / الدفاع عن البيانات فقط.
نصائح أخرى
الأحداث هي سيناريو شائع لمراجع ضعيفة.
مشكلة
النظر في زوج من الأشياء: باعث واستقبال. يحتوي المتلقي على حياة أقصر من باعث.
يمكنك تجربة تنفيذ مثل هذا:
class Emitter(object):
def __init__(self):
self.listeners = set()
def emit(self):
for listener in self.listeners:
# Notify
listener('hello')
class Receiver(object):
def __init__(self, emitter):
emitter.listeners.add(self.callback)
def callback(self, msg):
print 'Message received:', msg
e = Emitter()
l = Receiver(e)
e.emit() # Message received: hello
ومع ذلك، في هذه الحالة، يبقي باعث مرجعا إلى طريقة ملزمة callback
التي تحافظ على مرجع إلى جهاز الاستقبال. لذلك يبقي باعث الاستقبال على قيد الحياة:
# ...continued...
del l
e.emit() # Message received: hello
هذا هو في بعض الأحيان مزعجة. تخيل ذلك Emitter
هو جزء من بعض طراز البيانات الذي يدل عند تغيير البيانات و Receiver
تم إنشاؤه بواسطة نافذة حوار يستمع إلى هذه التغييرات لتحديث بعض عناصر التحكم في UI.
من خلال عمر التطبيق، يمكن إلغاء حواريات متعددة ولا نريد أن لا تزال مستقبلاتها مسجلا داخل باعث لفترة طويلة بعد إغلاق النافذة. سيكون ذلك تسرب ذاكرة.
إزالة الاحتياطات يدويا هو خيار واحد (تماما مثل مزعج)، باستخدام المراجع الضعيفة هو آخر.
المحلول
هناك فئة لطيفة WeakSet
يبدو أن مجموعة عادية، بل تخزن أعضائها باستخدام مراجع ضعيفة ولم تعد تخزنها عند تحريرها.
ممتاز! دعنا نستخدمها:
def __init__(self):
self.listeners = weakref.WeakSet()
وتشغيل مرة أخرى:
e = Emitter()
l = Receiver(e)
e.emit()
del l
e.emit()
أوه، لا يحدث شيء على الإطلاق! ذلك لأن الطريقة المحددة (جهاز استقبال معين callback
) يتيم الآن - لا باع باع باعث أو جهاز الاستقبال مرجعا قويا له. وبالتالي فإن القمامة التي تم جمعها على الفور.
دعونا نجعل المتلقي (وليس باعث هذه المرة) احتفظ بإشارة قوية إلى هذا الاتصال:
class Receiver(object):
def __init__(self, emitter):
# Create the bound method object
cb = self.callback
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
الآن يمكننا أن نلاحظ السلوك المتوقع: باعث يبقي فقط رد الاتصال طالما يعيش المستقبل.
e = Emitter()
l = Receiver(e)
assert len(e.listeners) == 1
del l
import gc; gc.collect()
assert len(e.listeners) == 0
تحت الغطاء
لاحظ أنني اضطررت لوضع gc.collect()
هنا للتأكد من أن جهاز الاستقبال يتم تنظيفه على الفور. هناك حاجة إلى هنا لأن هناك الآن دورة من المراجع القوية: تشير الطريقة الملزمة إلى جهاز الاستقبال والعكس صحيح.
هذا ليس سيئا للغاية؛ هذا يعني فقط أنه سيتم تأجيل تنظيف جهاز الاستقبال حتى يتم تشغيل جامع القمامة التالي. لا يمكن تنظيف المراجع الدورية من خلال آلية عد مرجعية بسيطة.
إذا كنت تريد حقا، فيمكنك إزالة الدورة المرجعية القوية عن طريق استبدال الطريقة المحددة كائن وظيفة مخصصة من شأنها أن تبقي لها self
كمرجع ضعيف أيضا.
def __init__(self, emitter):
# Create the bound method object
weakself = weakref.ref(self)
def cb(msg):
self = weakself()
self.callback(msg)
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
دعونا نضع هذا المنطق في وظيفة المساعد:
def weak_bind(instancemethod):
weakref_self = weakref.ref(instancemethod.im_self)
func = instancemethod.im_func
def callback(*args, **kwargs):
self = weakref_self()
bound = func.__get__(self)
return bound(*args, **kwargs)
return callback
class Receiver(object):
def __init__(self, emitter):
cb = weak_bind(self.callback)
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
الآن ليس هناك دورة من المراجع القوية، لذلك متى Receiver
يتم تحريره، وسيتم أيضا تحرير وظيفة رد الاتصال (وإزالتها من باعث WeakSet
) على الفور، دون الحاجة إلى دورة GC كاملة.
- المراجع الضعيفة هي مفهوم مهم في بيثون، وهو مفقود بلغات يحب Java (Java 1.5).
في نمط تصميم المراقب، يجب أن يحافظ كائن مريح بشكل عام على مراجع ضعيفة إلى كائن المراقب.
على سبيل المثال ينبعث من الحدث الذي تم إجراؤه () و B يسجل مع ذلك، فإنه يريد الاستماع إلى الحدث المنجز (). وبالتالي، عند الانتهاء () ينبعث، يتم إخطار B. ولكن إذا لم يكن هناك حاجة إلى B في التطبيق، فلا يجب أن تصبح تعرقلا في مجموعة القمامة في A (منذ تعليق الإشارة إلى B). وبالتالي، إذا كان هناك إشارة ضعيفة إلى B، وعندما تكون كل الإشارات إلى أ، فستكون B جمع القمامة.
- انها أيضا مفيدة جدا في تنفيذ مخابئ.