تُمكّن ديكوراتورات بايثون من إضافة سلوكيات مشتركة إلى دالة، مثل قياس المدة، وإعادة المحاولة، والتخزين المؤقت، أو التحقق من الوسائط، دون تعديل كودها الداخلي. في برنامج تعليمي نُشر على Machine Learning Mastery، يشرح المؤلف آلية عملها ويوضح كيفية بناء ديكوراتورات موثوقة، دون إخفاء الاستثناءات.
نقطة البداية هي وحدة معالجة بيانات تتكون من ثلاث دوال اكتسبت، مع مرور الأسابيع، مهامًا فرعية إضافية. وفقًا للبرنامج التعليمي، تقوم إحدى الدوال بالتحقق من وجود مسارها قبل تحميل التضمينات (embeddings)، بينما تكرر أخرى طلباتها في حلقة باستخدام time.sleep()، وتقيس الثالثة وقت تنفيذها وتحتفظ بنتائجها في قاموس. هذا الكود الهامشي، المُضاف يدويًا، يختلف من دالة إلى أخرى، وكل دالة جديدة قد تقدم تنوعًا خاصًا به.
الديكوراتور: مجرد تعيين بسيط
الديكوراتور هو دالة تأخذ دالة كوسيط وتُرجع غلافًا (wrapper)، أي غلافًا ينفذ كودًا قبل وبعد الاستدعاء الأصلي. يذكر البرنامج التعليمي أن صيغة @ تعادل إعادة تعيين اسم الدالة، وهو ما يُطلق عليه قاموس بايثون مصطلح ‘السكر النحوي’ (syntactic sugar). وبالتالي، لا يتم تنفيذ الديكوراتور إلا مرة واحدة، في وقت تعريف الدالة.
يحتفظ الغلاف بالدالة الأصلية بفضل آلية الإغلاق (closure)، وهي الآلية التي تتيح لدالة داخلية الوصول إلى متغيرات الدالة التي تحتويها. تنقل الوسائط *args و **kwargs الوسائط، ويتم إرجاع النتيجة كما هي. ومع ذلك، دون احتياطات إضافية، يستبدل الغلاف اسم الدالة ووثائقها. لذلك يوصي البرنامج التعليمي باستخدام functools.wraps، الذي ينسخ هذه البيانات الوصفية، بالإضافة إلى السمة __wrapped__، التي تتيح الوصول إلى الدالة غير المزخرفة أثناء الاختبارات.
أربعة سلوكيات هامشية
يعتمد ديكوراتور القياس على time.perf_counter()، وهي ساعة تُستخدم لحساب الفترات الزمنية وليس التواريخ. عند وضعه في كتلة try/finally، يعرض المدة حتى عندما تفشل الدالة، مع استمرار نقل الاستثناء إلى المُستدعي.
ديكوراتور إعادة المحاولة هو ‘مصنع’ (factory)، يقبل خيارات مثل max_attempts، وdelay والاستثناءات التي يجب إعادة محاولتها. يعيد المحاولة فقط للاستثناءات المُهيأة، حتى لا يتم التعامل مع خطأ برمجي على أنه مشكلة شبكة. في المحاولة الأخيرة، يؤدي استخدام raise بمفرده إلى إعادة رفع الاستثناء الأصلي. ومع ذلك، يسلط البرنامج التعليمي الضوء على قيد: هذا النهج آمن فقط للمكالمات التي يمكن تنفيذها مرتين دون عواقب. لا يفي الدفع أو إرسال الرسالة بهذا المعيار.
للتخزين المؤقت، يوصي البرنامج التعليمي بـ functools.lru_cache، الذي يحتفظ بنتائج أحدث الاستدعاءات، حتى حد maxsize (على سبيل المثال 256). ترتبط قيوده بتنفيذه: يجب أن تكون الوسائط قابلة للتجزئة (hashable)، وقد تنتج دالة تُرجع كائنًا قابلًا للتعديل تأثيرات غير متوقعة. أخيرًا، يستخدم التحقق inspect.signature() لمطابقة الوسائط بأسماء المعاملات، حتى عندما يتم تمريرها حسب الموضع أو بقيمتها الافتراضية، ثم يطبق قواعد بسيطة قبل الاستدعاء.
يختتم البرنامج التعليمي بقاعدة حذر: لا ينبغي للديكوراتور أن يقرر منطق العمل (business logic) للدالة. عندما يعتمد السلوك على البيانات، فمن الأفضل وضعه داخل الدالة، أو داخل نموذج بيانات للتحقق. عند تكديس عدة ديكوراتورات، يهم ترتيب تطبيقها، لأن الديكوراتور الأعلى هو الغلاف الأكثر خارجية.
