الدالة weakref.finalize()‎ في بايثون

من موسوعة حسوب
مراجعة 15:47، 27 أغسطس 2018 بواسطة Mohammed Taher (نقاش | مساهمات)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)


يعيد هذا الصنف كائن إنهاء finalizer قابل للاستدعاء، ويجري استدعاؤه عند استرجاع الكائن المعطى بواسطة مجموعة garbage.

البنية العامة

class weakref.finalize(obj, func, *args, **kwargs)

المعاملات

يعدّ كائن الإنهاء "حيًّا" إلى حين استدعائه (إما على نحو صريح أو في مجموعة garbage) وبعد ذلك يصبح "ميّتًا". تؤدي عملية استدعاء كائن إنهاء حيّ إلى إعادة نتيجة تنفيذ func(*arg, **kwarg)‎، في حين تعيد عملية استدعاء كائن إنهاء ميّت القيمة None.

من الضروري التأكّد من أن func و args و kwargs لا تمتلك أيّ إشارات إلى الكائن المعطى، سواء أكانت الإشارات مباشرة أو غير مباشرة، وإلا فإنّ الكائن المعطى لن يُجمع بواسطة مجموعة garbage. كذلك يجب أن لا تكون func على وجه الخصوص تابعًا مرتبطًا بالكائن المعطى.

القيمة المعادة

تعرض الاستثناءات التي تطلقها عمليات الاستدعاء الخلفي لكائنات الإنهاء أثناء عملية جمع القمامة في مخرجات الخطأ المعيارية، ولكن لا يمكن لها أن تتمدّد. ويجري التعامل مع هذه الاستثناء بنفس الطريقة المتّبعة مع الاستثناءات التي يطلقها تابع ‎__del__()‎ أو عملية استدعاء خلفية لإشارة خلفية.

عند إغلاق البرنامج، يجري استدعاء كائنات الإنهاء الحيّة كلها باستثناء تلك التي تأخذ الخاصية atexit فيها القيمة False، وتستدعى هذه الكائنات بعكس ترتيب إنشائها.

لن ينفّ كائن الإنهاء الاستدعاء الخلفي الخاصّ به أثناء الجزء الأخير من عملية إغلاق المفسّر عندما تكون الخصائص العامة globals في الوحدة عرضة لاستبدال قيمها بالقيمة None.

يعيد هذا الصنف كائن إنهاء finalizer قابل للاستدعاء، ويجري استدعاؤه عند استرجاع الكائن المعطى بواسطة مجموعة garbage. على العكس من الإشارات الضعيفة العادية، فإنّ كائن الإنهاء يبقى حيًّا إلى أن تسترجع مجموعة garbage كائن الإشارة، الأمر الذي يبسّط عملية إدارة دورة حياة الكائن.

كائنات الإنهاء

إنّ الفائدة الرئيسة من استخدام finalize هي أنّها تسهّل عملية تسجيل الاستدعاء الخلفي دون الحاجة إلى الاحتفاظ بكائن الإنهاء المعاد. فعلى سبيل المثال:

>>> import weakref
>>> class Object:
...     pass
...
>>> kenny = Object()
>>> weakref.finalize(kenny, print, "You killed Kenny!")  
<finalize object at ...; for 'Object' at ...>
>>> del kenny
You killed Kenny!

يمكن استدعاء كائن الإنهاء على نحو مباشر أيضًا، ولكنّه سينفّذ الاستدعاء الخلفية مرة واحدة على الأكثر.

>>> def callback(x, y, z):
...     print("CALLBACK")
...     return x + y + z
...
>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> assert f.alive
>>> assert f() == 6
CALLBACK
>>> assert not f.alive
>>> f()                     # لا يجري استدعاء الاستدعاء الخلفي لأنّ كائن الإنهاء ميت
>>> del obj                 # لا يجري استدعاء الاستدعاء الخلفي لأنّ كائن الإنهاء ميت

يمكن إلغاء تسجيل كائن إنهاء باستخدام التابع detach()‎. ينهي هذا التابع حياة كائن الإنهاء ويعيد المعاملات الممررة إلى الدالة البانية لحظة إنشائه.

>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> f.detach()                                           
(<...Object object ...>, <function callback ...>, (1, 2), {'z': 3})
>>> newobj, func, args, kwargs = _
>>> assert not f.alive
>>> assert newobj is obj
>>> assert func(*args, **kwargs) == 6
CALLBACK

يستدعى كائن الإنهاء إن كان حيًّا عند إنهاء عمل البرنامج ما لم تعيّن القيمة False للخاصية atexit:

>>> obj = Object()
>>> weakref.finalize(obj, print, "obj dead or exiting")  
<finalize object at ...; for 'Object' at ...>
>>> exit()                                               
obj dead or exiting

مقارنة كائنات الإنهاء مع توابع ‎__del__()‎

لنفرض أنّنا نرغب في إنشاء صنف تمثّل نسخه قواميس مؤقتة. يجب أن تُحذف هذه القواميس وكامل محتوياتها عند أول حدث من الأحداث التالية:

  • جمع الكائن في القمامة
  • استدعاء التابع remove()‎ الخاص بالكائن.
  • الخروج من البرنامج.

يمكن استخدام التابع ‎__del__()‎ كما يلي:

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()
    def remove(self):
        if self.name is not None:
            shutil.rmtree(self.name)
            self.name = None
    @property
    def removed(self):
        return self.name is None
    def __del__(self):
        self.remove()

بدءًا من الإصدار 3.4 من بايثون، لم تعد توابع ‎__del__()‎ تمنع دورات الإشارة من أن تُجمع في القمامة، ولم تعد تجبر الخصائص العامة للوحدة بأن تأخذ القيمة None عند إغلاق المفسّر؛ لهذا ستعمل هذه الشيفرة دون أي مشاكل في CPython.

ولكن طريقة التعامل مع توابع ‎__del__()‎ عائدة إلى طريقة الاستخدام؛ وذلك لأنّها تعتمد على تفاصيل داخلية خاصّة بجامع القمامة الخاصّ بالمفسّر.

البديل الأنسب هو تعريف كائن إنهاء يشير فقط إلى الدوال والكائنات التي يحتاجها، عوضًا عن الوصول إلى الكائن برمّته:

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()
        self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)
    def remove(self):
        self._finalizer()
    @property
    def removed(self):
        return not self._finalizer.alive

عند تعريف كائن الإنهاء بهذه الطريقة، فإنّه يستقبل فقط إشارة إلى التفاصيل التي يحتاجها لتنظيف المجلّد بصورة ملائمة. وحتى لو لم يُجمع الكائن في القمامة على الإطلاق، فإنّ كائن الإنهاء سيُستدعى عند الخروج من البرنامج. وتمتاز كائنات الإنهاء المبنية على كائنات weakref في إمكانية استخدامها لتسجيل كائنات الإنهاء للأصناف التي يتحكم طرف ثالث في تعريفها، كأن يجري تشغيل شيفرة عندما لا تكون الوحدة محمّلة:

import weakref, sys
def unloading_module():
# إشارة ضمنية إلى الخصائص العامة في الوحدة من داخل الدالة
weakref.finalize(sys.modules[__name__], unloading_module)

ملاحظة:

إن إنشأت كائن إنهاء في daemonic thread لحظة إنهاء البرنامج فمن المحتمل أن لا يُستدعى كائن الإنهاء عند إنهاء البرنامج. ولكن في daemonic thread فإنّ atexit.register()‎ و  try: ... finaly: ...‎ و with: ...‎ لا تضمن تنفيذ حدث التنظيف.

توابع كائنات الإنهاء

التابع ‎__call__()‎

إن كان self حيًّا فسيُعلّم من قبل التابع على أنّه ميت ويعيد التابع نتيجة استدعاء func(*args, **kwargs)‎. أما إن كان self ميّتًا فسيعيد التابع القيمة None.

التابع detach()‎

إن كان self حيًّا فسيعلّم من قبل التابع على أنّه ميت ويعيد التابع الصفّ (obj, func, args, kwargs). وإن كان self ميّتًا فسيعيد التابع القيمة None.

التابع peek()‎

إن كان self حيًّا فسيعيد التابع الصف (obj, func, args, kwargs). وإن كان self ميّتًا فسيعيد التابع القيمة None.

خصائص كائنات الإنهاء

الخاصية alive

تحمل هذه الخاصية القيمة True إن كان كائن finalizer حيًّا، وتأخذ القيمة False فيما عدا ذلك.

الخاصية atexit

خاصية بوليانية قابلة للكتابة وتأخذ القيمة True كقيمة افتراضية. يستدعي البرنامج عند إغلاقه جميع كائنات finalizer الحية التي تأخذ الخاصّية atexit فيها القيمة True، وتجري عملية الاستدعاء بعكس ترتيب إنشاء هذه الكائنات.

ملاحظة: هذه الخاصية جديدة في الإصدار 3.4 من بايثون.

مصادر

صفحة Weak references في توثيق بايثون الرسمي.