أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
البرمجة بالعربي

Decorators في Python للمتوسط: أضِف Logging و Timing بسطر واحد

📅 ١٠ مايو ٢٠٢٦⏱ 7 دقائق قراءة
Decorators في Python للمتوسط: أضِف Logging و Timing بسطر واحد

المستوى المطلوب: متوسط — تحتاج معرفة Python أساسية، فهم مفهوم الـ functions كـ first-class objects، وقدرة قراءة كود من 30 سطر بدون توقّف. لو لسه مبتدئ تمامًا، الأمثلة الأولى هتمشي معاك بمثال الفندق، لكن آخر قسمين فيهم تفاصيل لمن جرّب يكتب كود إنتاج فعلاً.

لو عندك 50 دالة في مشروع Python وعايز تضيف Logging و Timing لكلهم بدون ما تكرّر نفس السطور 50 مرة، Decorators بـ 8 سطور بتعملك ده. هنا بنشرح ليه الـ Decorator مش "ميزة شيك للكود الجميل"، هو أداة هندسية لفصل الـ cross-cutting concerns عن منطق العمل، وبيوفّر ساعات حقيقية في الـ debugging.

شاشة لابتوب تعرض كود Python فيه decorator مع رمز @ واضح

Decorators في Python: المفهوم في جملة

الـ Decorator هو دالة بتاخد دالة تانية وبترجّع نسخة معدّلة منها بدون تغيير كود الدالة الأصلية. كده ببساطة. لو عايز تضيف سلوك (logging، timing، caching، retry، authorization)، بتكتب الكود مرة وبتلصقه فوق أي دالة بسطر واحد @my_decorator.

المثال للمبتدئ: موظف الاستقبال في الفندق

تخيّل فندق فيه 50 موظف، كل واحد بيقدّم خدمة معيّنة (تنظيف، طلب طعام، صيانة، نقل شنط). الإدارة قرّرت إن أي موظف لازم قبل ما يقدّم أي خدمة: يسأل النزيل عن رقم غرفته، يكتب اسمه في سجل، ويعدّ الوقت اللي خد الخدمة. عندك خياران:

  1. تروح لكل موظف من الـ 50 وتعدّل سكريبت شغله. مجهود 50 مرة، وأي تغيير بعدين بيتكرّر 50 مرة تاني، وفي حد نَسي يعدّل سكريبت الموظف رقم 37.
  2. تحط موظف استقبال واحد على باب كل قسم. النزيل بيدخل عليه الأول، يسأله، يسجّل، وبعدين يدخّله للموظف الأصلي. عدّل موظف الاستقبال مرة واحدة وكل القسم اتغيّر.

الـ 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 مركزي

Python
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'

السطر @log_call فوق charge_card بيلفّها بـ wrapper بيطبع قبل وبعد التنفيذ. لو عندك 50 دالة، تحط نفس السطر فوق كل واحدة. الميزة: لو عايز تغيّر الفورمات بكرة (تضيف timestamp مثلًا)، بتعدّل سطرين في log_call بدل 50 مكان.

دالة Python ملفوفة بطبقة wrapper تمثل عملية التزيين decorator

الفخ الكلاسيكي: ليه functools.wraps واجبة؟

لو شيلت السطر @functools.wraps(func)، الـ wrapper هيخفي الميتاداتا الأصلية للدالة. charge_card.__name__ هترجّع "wrapper" بدل "charge_card"، والـ __doc__ هتختفي، وأي tooling زي Sphinx أو pytest أو FastAPI الـ introspection بتاعهم هيتشوّش.

مثال على الكسر: FastAPI بيستخدم اسم الدالة لتوليد OpenAPI schema. لو decoratorاتك مش بتستخدم @functools.wraps، كل endpoints الـ API هتظهر باسم "wrapper" في توثيق Swagger. functools.wraps بتنسخ __name__ و __doc__ و __annotations__ و __wrapped__ من الأصل للـ wrapper. اعتبرها واجبة، مش اختيارية.

Decorator بمعاملات: Timing مع threshold

Python
import functools
import time

def time_it(threshold_ms: float = 0):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            elapsed_ms = (time.perf_counter() - start) * 1000
            if elapsed_ms >= threshold_ms:
                print(f"{func.__name__} took {elapsed_ms:.2f}ms")
            return result
        return wrapper
    return decorator

@time_it(threshold_ms=100)
def slow_query(rows: int) -> list:
    time.sleep(rows / 1000)
    return list(range(rows))

slow_query(50)    # ساكت (أقل من 100ms)
slow_query(500)   # slow_query took 503.41ms

هنا 3 طبقات دوال بدل 2. السبب إن @time_it(threshold_ms=100) بيستدعي time_it(100) الأول، اللي بيرجّع decorator فعلي يقبل الدالة. القاعدة العامة: كل قوس فاضي بعد الـ @ معناه طبقة دالة جوّاية إضافية. لو الـ decorator بدون معاملات تبقى الطبقات اتنين. بمعاملات، تبقى ثلاثة.

سيناريو واقعي: Endpoints الدفع في خدمة Python

الافتراض: خدمة FastAPI فيها 12 endpoint للدفع، كل endpoint بينادي 3-5 دوال داخلية للـ DB والـ payment gateway. حصل bug في endpoint الـ refund وأخدت 4 ساعات ندوّر فين البطء، لحد ما لقينا إن استعلام واحد على جدول الـ transactions بياخد 1.8 ثانية لأنه مش index.

بعد ما حطّينا @time_it(threshold_ms=200) فوق كل دوال DB layer (38 دالة)، أي طلب أبطأ من 200ms بقى يطبع نفسه في الـ logs مع اسم الدالة الفعلي وزمنها بالظبط. النتيجة المقاسة على آخر sprint:

  • الـ MTTD (Mean Time To Detect) للـ regression نزل من 4 ساعات لـ 6 دقائق.
  • عدد الـ slow queries المكتشفة قبل الإنتاج زاد من 2 لـ 14 (في 3 أسابيع).
  • التكلفة: ~120ns إضافية لكل function call. على 4,200 طلب/ثانية في الذروة، ده 0.5ms CPU زيادة كل ثانية. لا مادي ولا محسوس.

Trade-offs الحقيقية

  • Stack trace أعمق: كل decorator بيضيف frame في الـ traceback. مع 4 decorators على نفس الدالة، أي exception بقت محتاجة قراءة 8 frames قبل ما توصل للسطر الحقيقي. التكلفة: ثواني زيادة في debug. المكسب: كود نظيف بدون تكرار.
  • Performance overhead: القياس على Python 3.12 (CPython): function call عادي ~80ns، function call مزخرف ~200ns. أي overhead 150% تقريبًا. لو الدالة بتتنادى مليون مرة في الثانية، ده 0.12 ثانية CPU زيادة. لو الدالة بتاخد ميلي ثانية أصلاً، الزيادة غير محسوسة.
  • Type checking: بعض الـ static type checkers (Mypy قبل 1.0) كانت بتفقد type info للدالة المزخرفة. Mypy 1.10+ و pyright بقوا أحسن مع ParamSpec من PEP 612، لكن decorators بمعاملات لسه فيها edge cases.
  • الإخفاء الخفي: القارئ بيشوف @retry(max=3) ومش عارف هيبقى السلوك إيه بدون ما يفتح كود retry. كل decorator بيخفي منطق. وثّقه في docstring واضحة، أو ضع تعليق فوقه يقول "بيعمل retry 3 مرات بـ exponential backoff".

متى لا تستخدم Decorator

  • السلوك مطلوب في 1-2 دالة فقط. كتابة الكود مباشرة أوضح من بناء تجريد decorator كامل.
  • السلوك بيغيّر signature الدالة (بيضيف أو يشيل arguments). ده بيعمل مفاجآت للمستدعي و type checker بيكسر.
  • محتاج تشتغل على class بكامله. استخدم __init_subclass__ أو metaclass بدل ما تلفّ كل method يدويًا.
  • السلوك conditional حسب runtime input بشكل معقّد. مكانه داخل الدالة كـ if-statement، مش فوقها كـ decorator.
  • محتاج تعدّل الدالة على مستوى الـ AST (مثل تحويل sync لـ async). استخدم metaclass، أو code generation، أو library متخصص زي asgiref.sync.

الخطوة التالية

افتح أكبر ملف عندك فيه دوال بتنادي DB أو API خارجي. اكتب @time_it(threshold_ms=300) فوق 5 منهم بس. شغّل الـ tests وشوف الـ output. لو لقيت دالة بتاخد أكتر من 300ms من غير ما تكون كده في خاطرك، أنت كسبت قيمة فورية في 5 دقايق. بعد كده عمم على باقي الـ codebase تدريجيًا، ولا تقع في فخ تزيين كل دالة بكل decorator موجود.

المصادر

  • PEP 318 — Decorators for Functions and Methods
  • PEP 612 — Parameter Specification Variables (ParamSpec)
  • Python docs — functools.wraps
  • Python Glossary — decorator
  • Python Reference — Function definitions and decorator semantics
  • Real Python — Primer on Python Decorators
  • FastAPI docs — Path Operation Configuration (يستخدم اسم الدالة)

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة