الدالة weakref.finalize()
في بايثون
يعيد هذا الصنف كائن إنهاء 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 من بايثون.