المستوى المطلوب: متوسط — تحتاج معرفة Python أساسية، فهم مفهوم الـ functions كـ first-class objects، وقدرة قراءة كود من 30 سطر بدون توقّف. لو لسه مبتدئ تمامًا، الأمثلة الأولى هتمشي معاك بمثال الفندق، لكن آخر قسمين فيهم تفاصيل لمن جرّب يكتب كود إنتاج فعلاً.
لو عندك 50 دالة في مشروع Python وعايز تضيف Logging و Timing لكلهم بدون ما تكرّر نفس السطور 50 مرة، Decorators بـ 8 سطور بتعملك ده. هنا بنشرح ليه الـ Decorator مش "ميزة شيك للكود الجميل"، هو أداة هندسية لفصل الـ cross-cutting concerns عن منطق العمل، وبيوفّر ساعات حقيقية في الـ debugging.
Decorators في Python: المفهوم في جملة
الـ Decorator هو دالة بتاخد دالة تانية وبترجّع نسخة معدّلة منها بدون تغيير كود الدالة الأصلية. كده ببساطة. لو عايز تضيف سلوك (logging، timing، caching، retry، authorization)، بتكتب الكود مرة وبتلصقه فوق أي دالة بسطر واحد @my_decorator.
المثال للمبتدئ: موظف الاستقبال في الفندق
تخيّل فندق فيه 50 موظف، كل واحد بيقدّم خدمة معيّنة (تنظيف، طلب طعام، صيانة، نقل شنط). الإدارة قرّرت إن أي موظف لازم قبل ما يقدّم أي خدمة: يسأل النزيل عن رقم غرفته، يكتب اسمه في سجل، ويعدّ الوقت اللي خد الخدمة. عندك خياران:
- تروح لكل موظف من الـ 50 وتعدّل سكريبت شغله. مجهود 50 مرة، وأي تغيير بعدين بيتكرّر 50 مرة تاني، وفي حد نَسي يعدّل سكريبت الموظف رقم 37.
- تحط موظف استقبال واحد على باب كل قسم. النزيل بيدخل عليه الأول، يسأله، يسجّل، وبعدين يدخّله للموظف الأصلي. عدّل موظف الاستقبال مرة واحدة وكل القسم اتغيّر.
الـ Decorator هو موظف الاستقبال ده بالظبط. بيلفّ الدالة الأصلية ويضيف سلوك قبل/بعد التنفيذ من غير ما يلمس كود الدالة. ده اسمه في علم البرمجيات Separation of Concerns: منطق العمل في مكان، والـ logging/timing/auth في مكان تاني.
التعريف العلمي الدقيق
حسب PEP 318 (المقترح اللي ضمّ Decorators للغة Python في 2003)، الـ Decorator هو callable بياخد callable وبيرجّع callable. السطر @my_decorator فوق def f(): مكافئ تمامًا لكتابة f = my_decorator(f). ده شكل من Higher-Order Functions، وبيستفيد من إن Python بتعامل الدوال كـ first-class objects (تقدر تمرّرها كـ argument، تخزّنها في list، أو ترجّعها من دالة تانية، زي أي قيمة عادية).
أبسط Decorator يشتغل: Logging مركزي
import functools
import logging
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
log.info(f"calling {func.__name__} args={args} kwargs={kwargs}")
result = func(*args, **kwargs)
log.info(f"{func.__name__} returned {result!r}")
return result
return wrapper
@log_call
def charge_card(user_id: int, amount: float) -> str:
return f"charged user {user_id} amount {amount}"
charge_card(42, 199.5)
# INFO:__main__:calling charge_card args=(42, 199.5) kwargs={}
# INFO:__main__:charge_card returned 'charged user 42 amount 199.5'