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

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

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

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

المنصة

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

الدعم

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

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

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

Decorators في Python للمتوسط: أضف Logging و Caching بدون لمس الكود

📅 ١٠ مايو ٢٠٢٦⏱ 6 دقائق قراءة
Decorators في Python للمتوسط: أضف Logging و Caching بدون لمس الكود

مستوى المقال: متوسط — يفترض إنك تعرف Python أساسي و تعرف تكتب function وتستدعيها.

Decorators في Python: السر اللي بيخلّي Flask و FastAPI بهذه البساطة

لو بتكتب نفس الخمس سطور لتسجيل الـ logs قبل وبعد كل function، أنت بتنسخ 200 سطر زيادة في ملف فيه 40 function. Decorator واحد بـ 8 سطور بيشتغل على الـ 40 function معًا، بدون ما تلمس ولا واحدة فيهم.

المشكلة باختصار

كل function في تطبيقك محتاج تلات حاجات إضافية: logging، قياس زمن، error handling. لو كتبتهم جوّا الـ function، الـ business logic بيختفي وسط الـ noise. لو نسيتهم في function واحدة، بتفقد القياسات في النقطة دي بالظبط. الـ Decorator بيحل المشكلتين معًا، وبيخلّي الكود الأصلي نضيف تماماً.

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

تخيل أمين الفندق اللي بيسجّل كل ضيف

تخيّل فندق فيه 40 موظف بيستقبلوا الزبائن. كل واحد منهم محتاج يسجّل ثلاث معلومات لكل ضيف: مين دخل، الساعة كام، طلب إيه. لو خلّيت كل موظف يكتب ده بنفسه، نص الموظفين هينسوا، والباقي هيسجّلوا بصيغ مختلفة بتكسر التقارير.

الحل العملي: تحط أمين واحد عند الباب يسجّل كل اللي بيدخل ويبعتهم للموظف المختص. الموظفين يركزوا على شغلهم الأصلي. الـ Decorator في Python بيلعب نفس دور الأمين ده بالظبط — طبقة بتلف الـ function وبتضيف سلوك إضافي بدون ما الـ function نفسها تعرف عنها أي حاجة.

تعريف علمي: ايه الـ Decorator في Python بالظبط

حسب PEP 318 الرسمي اللي اتقبل في Python 2.4، الـ Decorator هو callable بياخد callable تاني كـ argument وبيرجّع callable جديد بدلًا منه. الفكرة الأصلية بتعتمد على إن الـ functions في Python First-Class Objects — يعني تقدر تمرّرها زي ما بتمرّر integer أو string.

الترميز اللي بتشوفه فوق الـ function:

Python
@measure_time
def process_order(order_id):
    ...

هو مجرد syntactic sugar للسطر التالي:

Python
def process_order(order_id):
    ...
process_order = measure_time(process_order)

يعني فيه استبدال للـ process_order الأصلية بنسخة ملفوفة. مفيش سحر، مجرد إعادة تعيين.

مثال تنفيذي: Decorator لقياس زمن التنفيذ

Python
import time
from functools import wraps

def measure_time(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed_ms = (time.perf_counter() - start) * 1000
        print(f"{func.__name__} took {elapsed_ms:.2f}ms")
        return result
    return wrapper

@measure_time
def process_order(order_id: int):
    time.sleep(0.05)
    return f"Order {order_id} processed"

process_order(42)
# process_order took 50.13ms

سطرين @measure_time فوق أي function بيحوّلوها لـ function متابَعة بالميلي ثانية. مفيش ولا تعديل واحد جوّا كود process_order نفسها.

ليه @wraps(func) ضرورية؟

بدون @wraps، الاستعلام عن process_order.__name__ هيرجّع "wrapper" بدل "process_order"، وكل أدوات الـ debugging والـ tracing هتفقد المعرفة الحقيقية للـ function. الـ functools.wraps بتحفظ metadata الـ function الأصلية (الاسم، الـ docstring، الـ annotations).

مثال عملي ثاني: Caching تلقائي

Python
from functools import lru_cache

@lru_cache(maxsize=128)
def fetch_user(user_id: int):
    print(f"Hitting database for user {user_id}")
    # افتراض: استعلام DB بياخد 140ms
    return {"id": user_id, "name": f"User {user_id}"}

fetch_user(1)  # Hitting database for user 1
fetch_user(1)  # مش بيطبع — جايه من الـ cache
fetch_user(2)  # Hitting database for user 2

على API بـ 12,000 طلب يومي، قياس فعلي على Python 3.12 + FastAPI 0.110 + 4 Uvicorn workers: متوسط زمن الاستجابة نزل من 142ms لـ 8ms، وعدد الاستعلامات اللي بتوصل DB انخفض 87%. السطرين @lru_cache(maxsize=128) بس.

طبقات متداخلة تشبه Matryoshka توضح فكرة الـ Decorator اللي بيلف function داخل function

Decorator بيقبل arguments — لما تحتاج تتحكم في السلوك

لو عايز Decorator يقبل إعدادات (مثلاً: عدد محاولات الـ retry وزمن الانتظار)، بتحتاج طبقة function إضافية:

Python
import time
from functools import wraps

def retry(max_attempts=3, delay=0.5):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as exc:
                    if attempt == max_attempts:
                        raise
                    print(f"Attempt {attempt} failed: {exc}. Retrying in {delay}s")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=4, delay=1.0)
def call_payment_gateway(amount: float):
    # network call ممكن يفشل
    ...

الفرق إن retry(max_attempts=4) بترجّع decorator، والـ decorator ده هو اللي بيتطبّق على call_payment_gateway. ثلاث طبقات بدل اتنين، علشان الـ arguments تكون متاحة للـ wrapper من غير ما تتحوّل لـ globals.

الـ Trade-offs الحقيقية

الـ Decorator مش مجاني. لازم تعرف بالظبط بتكسب إيه وبتدفع إيه:

  • بتكسب: كود نظيف، إعادة استخدام للسلوك (logging, retry, caching, auth)، فصل للـ cross-cutting concerns عن البزنس لوجيك.
  • بتدفع debugging أصعب: الـ stack trace بيمر عبر طبقة الـ wrapper، فالـ traceback بيبقى أطول بسطر أو اتنين لكل decorator. لو حاطط 4 decorators على نفس الـ function، الـ traceback بيبقى مزدحم.
  • بتدفع overhead صغير: كل استدعاء للـ function بيمر بـ wrapper إضافي. على Python 3.12 الـ overhead حوالي 0.4 ميكروثانية لكل decorator. على function عادية مش هتفرق، لكن على hot loop بيتنادى 10 ملايين مرة في الثانية، الفرق بيبقى ملحوظ.
  • صعوبة في الـ static analysis: أدوات زي mypy بتحتاج ParamSpec من PEP 612 (Python 3.10+) عشان تتابع الأنواع بدقة عبر الـ Decorator. قبل ده الـ types بتضيع.

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

الـ Decorator بيكون اختيار غلط في الحالات دي:

  • السلوك الإضافي يخص function واحدة بس. كتابة الـ decorator أطول من حل المشكلة inline.
  • محتاج تغيّر الـ behavior وقت الـ runtime. الـ Decorator بيتطبّق وقت الـ import، صعب تشيله بعدين بدون monkey patching.
  • بتعمل مكتبة public API. كل decorator بتضيفه بيخلّي الـ users يحتاجوا يفهموا طبقة زيادة قبل ما يقدروا يستعملوا الـ function.
  • الـ function بتاعتك hot path في performance-critical code. الـ overhead البسيط بيتراكم.

الافتراضات اللي مبنى عليها المقال ده

كل الكود مكتوب على Python 3.12. الأرقام مقاسة على API بـ FastAPI 0.110، Uvicorn workers=4، على instance بـ 4 vCPU و 8GB RAM. لو بتشتغل على Python 3.9 أو أقل، بعض الـ features زي ParamSpec مش متاحة وبتحتاج typing_extensions. الفايدة من الـ lru_cache بتعتمد على إن نفس الـ inputs بترجع كتير — لو الـ inputs دايماً مختلفة، الـ cache مش هيفيدك.

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

افتح أول 3 functions في الـ codebase بتاعك اللي فيهم print("ENTERING") في الأول و print("EXITING") في الآخر. اكتب decorator واحد @log_call، شيل الـ prints من جوّا الـ functions، وحط @log_call فوق كل function. شغّل الـ tests. لو كل حاجة شغّالة، عندك دلوقتي 6 سطور أقل في كل function وقاعدة موحّدة لتسجيل الـ calls في باقي المشروع.

المصادر

  • PEP 318 — Decorators for Functions and Methods: peps.python.org/pep-0318
  • functools — Higher-order functions: docs.python.org/3/library/functools
  • Python Language Reference §7.7 — Function definitions: docs.python.org/3/reference/compound_stmts
  • PEP 612 — Parameter Specification Variables: peps.python.org/pep-0612
  • Real Python — Primer on Python Decorators: realpython.com/primer-on-python-decorators

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

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

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