شرح Decorators في Python: طريقة عملية لتوسيع الدوال من دون تكرار
تظهر Decorators في Python كثيراً داخل أطر الويب والاختبارات، لكنها تبدو غامضة لمن يراها أول مرة. الفكرة أبسط من شكلها: نمرر دالة إلى دالة أخرى، ثم نعيد نسخة تضيف سلوكاً قبل التنفيذ أو بعده. في هذا المقال نبني Decorator صغيراً لقياس زمن الدالة وتسجيل اسمها، ثم نوضح معنى functools.wraps ومتى يصبح استخدام هذا الأسلوب مفيداً. الهدف ليس حفظ علامة @، بل فهم العلاقة بين الدوال كقيم وبين إعادة استخدام السلوك بطريقة لا تلوث كل دالة بتفاصيل جانبية.
الفكرة الأساسية
في Python يمكن تخزين الدالة في متغير وتمريرها إلى دالة أخرى وإعادتها كنتيجة. يستفيد Decorator من هذه الخاصية ليضع غلافاً حول الدالة الأصلية. الغلاف يستقبل نفس المعاملات عبر *args و **kwargs، ينفذ ما يحتاجه، ثم يستدعي الدالة الأصلية. استخدام wraps يحافظ على اسم الدالة ووصفها، وهو مهم عندما تعرض أدوات الاختبار أو التوثيق معلومات عن الكود.
مثال برمجي عملي
from functools import wraps
from time import perf_counter
def measure_time(function):
@wraps(function)
def wrapper(*args, **kwargs):
started = perf_counter()
result = function(*args, **kwargs)
elapsed = perf_counter() - started
print(f"{function.__name__}: {elapsed:.4f}s")
return result
return wrapper
@measure_time
def total_price(values):
return sum(values)
print(total_price([12, 8, 20]))
شرح المثال
يستقبل measure_time الدالة الأصلية ويعيد wrapper بدلاً منها. عند استدعاء total_price، ينفذ البرنامج النسخة المغلفة، فيسجل وقت البداية ثم يشغل الدالة ويعيد نتيجتها كما هي. لا ينبغي أن يغير Decorator القيمة أو الاستثناءات إلا إذا كان ذلك مقصوداً. الدوال التي تحفظ سلوكها الأصلي أسهل في الاختبار والتشخيص، ولهذا استوردنا wraps.
أنشئ ملفاً باسم decorators_demo.py وشغله من الطرفية. جرّب وضع decorator على دالة تأخذ معاملات مسماة، ثم اختبر ما يحدث عندما ترفع الدالة استثناءً. بعد فهم المثال، استخدم الفكرة في تسجيل الطلبات أو التحقق من صلاحية المستخدم، لكن ضع السلوك المشترك في Decorator واحد واضح بدلاً من نسخ الطباعة داخل كل دالة.
طريقة التفكير قبل التنفيذ
قبل كتابة الكود، حدد المشكلة في جملة واحدة، ثم اكتب المدخلات والمخرجات والحالات التي قد تفشل. هذا الترتيب يمنعك من القفز إلى مكتبة أو إطار قبل فهم ما يحتاجه التطبيق فعلاً. قسّم الحل إلى أجزاء صغيرة، واجعل لكل جزء مسؤولية يمكن شرحها واختبارها. لا تعني البساطة حذف كل التفاصيل، بل وضع التفاصيل في المكان الذي يحتاجها.
من المفيد أيضاً أن تكتب مثالاً يدوياً للنتيجة المتوقعة. عندما ترى البيانات أمامك، ستلاحظ حقولاً ناقصة أو حالات لم تكن في بالك. احتفظ بهذه الملاحظات بجانب المشروع، وحدّثها إذا تغير التصميم. التوثيق القصير في البداية يوفر وقتاً كبيراً عندما يعود الفريق إلى الكود بعد أسابيع.
اختبار الفكرة في حالات مختلفة
ابدأ بالمسار الطبيعي، ثم جرّب قيمة فارغة، ومدخلاً كبيراً، وبيانات غير مرتبة، وفشلاً في الاتصال أو التخزين. لا يكفي أن يعمل المثال مرة واحدة على جهازك؛ المطلوب أن تعرف كيف يتصرف عندما لا تسير الأمور كما تتوقع. اجعل الاختبارات قابلة للإعادة، ولا تعتمد على وقت متغير أو خدمة خارجية إذا كان ذلك غير ضروري.
- تحقق من البيانات قبل تمريرها إلى الجزء الذي يعالجها.
- استخدم رسائل خطأ تشرح المشكلة من دون كشف أسرار النظام.
- اختبر المسار الناجح والمسارات التي تعيد نتيجة فارغة.
- سجّل القرار الذي اتخذته عندما توجد أكثر من طريقة للحل.
ملاحظات على جودة المشروع
تظهر قابلية الصيانة في أسماء واضحة، ودوال قصيرة، وحدود مفهومة بين طبقات التطبيق. لا تضع الإعدادات الخاصة بجهازك داخل الكود، ولا تخزن مفاتيح الوصول في المستودع. استخدم ملفاً نموذجياً للإعدادات، واترك القيم الحقيقية خارج الملفات التي تشاركها مع الآخرين. راجع الكود بعد أن يعمل، لأن النسخة الأولى تركز غالباً على الوصول إلى النتيجة لا على وضوح الطريق إليها.
متى يحتاج الحل إلى تطوير؟
يكفي المثال الصغير للتعلم، لكنه لا يغطي كل ما يحتاجه نظام حقيقي. عند زيادة المستخدمين أو البيانات ستظهر الحاجة إلى مراقبة، وصلاحيات، واختبارات آلية، وسياسة واضحة للتعامل مع الفشل. أضف هذه الأجزاء عندما تظهر مشكلة تبررها، ولا تنسخ بنية كبيرة إلى مشروع صغير بلا سبب. التصميم الجيد قابل للنمو، لكنه لا يحاول توقع كل شيء منذ اليوم الأول.
أخطاء ينبغي تجنبها
من الأخطاء الشائعة نسيان إرجاع نتيجة الدالة الأصلية، فيصبح الناتج None، أو كتابة wrapper لا يقبل المعاملات المسماة. لا تستخدم Decorator لإخفاء منطق أعمال طويل، ولا تجعل الغلاف يلتقط كل الاستثناءات من دون تسجيل أو إعادة رفع. إذا احتاج السلوك إلى بيانات كثيرة، فقد تكون فئة أو خدمة مستقلة أوضح.
خطوة تالية مناسبة
جرّب بناء Decorator يتحقق من أن قائمة القيم غير فارغة، ثم أضف Decorator آخر يسجل عدد الاستدعاءات. راقب ترتيب التنفيذ عندما تضع أكثر من Decorator فوق الدالة نفسها، واكتب اختباراً يثبت أن النتيجة والاسم والاستثناءات لم تتغير من دون سبب.
الحدود التي يجب أن تضعها حول Decorator
عند استخدام Decorator في مشروع حقيقي، اسأل أولاً عن السلوك الذي يتكرر فعلاً. تسجيل الزمن أو التحقق من الصلاحية مثالان مناسبان لأنهما لا ينتميان إلى منطق الدالة نفسها. أما وضع قواعد العمل داخل الغلاف فقد يجعل قراءة الدالة مضللة، إذ سيحتاج المطور إلى فتح ملف آخر حتى يعرف ما يحدث قبل التنفيذ. اجعل اسم الغلاف واضحاً، ووثق ما يضيفه، وقرر هل يرفع الاستثناء كما هو أم يحوله إلى خطأ مفهوم. إذا كان ترتيب الأغلفة مهماً، اكتبه في اختبار صغير بدلاً من الاعتماد على الذاكرة. بهذه الطريقة تبقى الميزة المضافة قابلة للإزالة عندما تتغير المتطلبات.
اكتب ملاحظة قصيرة عن القرار الذي اتخذته أثناء التجربة، وسجل ما قسته أو اختبرته بدلاً من الاكتفاء بانطباع عام. قارن النسخة البسيطة بالنسخة التي أضفت إليها هذه الخطوة، ثم اسأل هل تحسن الوضوح أو الأداء أو سهولة الصيانة. إذا لم يظهر أثر عملي، ارجع إلى التصميم الأبسط. هذه المراجعة تمنع تحول المثال التعليمي إلى تعقيد ثابت لا يخدم المستخدم.
خطة تطبيق عملية
بعد فهم موضوع «شرح Decorators في Python: طريقة عملية لتوسيع الدوال من دون تكرار»، لا تنقل المثال إلى مشروع كبير دفعة واحدة. أنشئ مجلداً صغيراً، وثبت نسخة الأدوات التي ستستخدمها، ثم اكتب حالة نجاح واحدة يمكن تشغيلها من البداية إلى النهاية. احتفظ بالمدخل الذي استخدمته والنتيجة التي توقعتها، لأن ذلك يمنحك نقطة مقارنة عندما تغير الكود. إذا كان الموضوع يعتمد على خدمة أو قاعدة بيانات، جهز بيانات تجريبية لا تحمل معلومات حقيقية، واكتب طريقة تنظيفها بعد انتهاء الاختبار.
انتقل بعد ذلك إلى الحالات التي تسبب الالتباس. ماذا يحدث عندما تكون القيمة فارغة؟ كيف يتصرف البرنامج عند وصول نوع غير متوقع؟ هل تعود رسالة مفيدة إذا توقفت الخدمة الخارجية أو لم يجد التطبيق السجل المطلوب؟ اكتب إجابة لكل سؤال في اختبار أو ملاحظة قصيرة. لا تحاول معالجة كل احتمال في سطر واحد؛ فصل المسارات يجعل التصحيح أسهل ويمنع إخفاء المشكلة خلف استثناء عام.
من المفيد أن تقيس قبل التحسين وبعده. قد يكون القياس زمناً أو حجماً أو عدد استدعاءات أو نسبة أخطاء، بحسب طبيعة الموضوع. لا تعتمد على الانطباع وحده، ولا تقارن تشغيلين مختلفين من دون تثبيت الظروف قدر الإمكان. إذا لم يتحسن المؤشر، تراجع عن التغيير وابحث عن السبب بدلاً من إضافة طبقة أخرى. وبعد أن تستقر النتيجة، اكتب README قصيراً يوضح أمر التشغيل، المدخلات، المخرجات، وأهم قرار اتخذته أثناء البناء.
أخيراً، راجع حدود المثال. الكود التعليمي يشرح المفهوم، لكنه قد يحتاج في الإنتاج إلى صلاحيات، ومراقبة، واختبارات، وإدارة أسرار، وسياسة للتحديث. أضف ما يبرره الاستخدام الفعلي فقط. بهذه الطريقة تتعلم التقنية من دون أن تخلط بين نموذج صغير ونظام جاهز للمستخدمين.
الخلاصة
جرّب بناء Decorator يتحقق من أن قائمة القيم غير فارغة، ثم أضف Decorator آخر يسجل عدد الاستدعاءات. راقب ترتيب التنفيذ عندما تضع أكثر من Decorator فوق الدالة نفسها، واكتب اختباراً يثبت أن النتيجة والاسم والاستثناءات لم تتغير من دون سبب. يوضح هذا الموضوع كيف تتحول الفكرة النظرية إلى خطوات يمكن تشغيلها وفحصها.
ابدأ بتطبيق المثال على ملف صغير، ثم غيّر مدخلاً واحداً وراقب النتيجة. بعد ذلك أضف حالة فشل واكتب اختباراً لها، ثم انقل الفكرة إلى مشروعك الحقيقي بحذر. عندما تفهم سبب كل خطوة، ستستطيع تغيير الأدوات أو اللغة من دون فقدان المفهوم. البرمجة تتحسن بالمحاولات القصيرة والمراجعة المستمرة، لا بنسخ كود طويل من دون معرفة ما الذي يحميه أو ما الذي قد يكسره.