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

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

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

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

المنصة

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

الدعم

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

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

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

Decorators في Python: حوّل دوالك العادية لأدوات بتقيس وتسجّل تلقائيًا

📅 ٢٦ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
Decorators في Python: حوّل دوالك العادية لأدوات بتقيس وتسجّل تلقائيًا

Decorators في Python: حوّل دوالك العادية لأدوات بتقيس وتسجّل تلقائيًا

المستوى المطلوب: متوسط — المقال ده يفترض إنك مرتاح مع تعريف الدوال وتمرير المعاملات في Python، لكن لسه مش متعمق في الـ functional programming. لو لسه مبتدئ تمام، فيه قسم في الأول هيشرحلك الفكرة بمثال يومي قبل ما ندخل في الجزء التقني.

لو بتكتب نفس سطور الـ print("entered function X") وstart = time.time() في كل دالة عايز تراقبها، أنت بتدفع تكلفة صيانة كان ممكن تختفي بسطر واحد فوق الدالة. الـ Decorator في Python بيخليك تضيف logging أو timing أو auth لأي دالة بدون ما تلمس جسمها الأصلي.

شاشة محرر كود تعرض دالة Python محاطة بـ decorators متعددة بترتيب رأسي

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

عندك 30 endpoint في تطبيق Flask أو FastAPI. كل واحد محتاج: سطر يطبع وقت الدخول، سطر يقيس الزمن المستغرق، سطر يتأكد إن المستخدم مسجّل دخول. لو نسخت الـ 3 سطور دول جوّا كل دالة، أي تعديل بسيط بعد كده هيخليك تفتح 30 ملف. ده بالظبط الموقف اللي الـ Decorator اتعمل عشانه.

اشرحلي الفكرة كأني مبتدئ خالص

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

الـ Decorator بيعمل نفس الفكرة بالظبط: الدالة الأصلية ما تتغيرش، إنما بتتلف بدالة تانية بتضيف سلوك قبلها أو بعدها. وزي ما تقدر تحط غطا فوق غطا فوق كوب، تقدر تحط Decorator فوق Decorator فوق دالة.

التعريف العلمي الدقيق

الـ Decorator هو دالة (أو class) بتاخد دالة كمعامل، وبترجّع دالة جديدة بتضيف سلوك حول الأصلية. ده ممكن لأن الدوال في Python first-class objects: تقدر تمررها وترجّعها وتسندها لمتغير. الـ syntax بـ @decorator_name فوق الدالة هو مجرد اختصار لكتابة my_func = decorator_name(my_func).

مثال تنفيذي: timer + logger في 25 سطر

Python
import time
import functools
import logging

logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(message)s")
log = logging.getLogger("app")

def measure(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            result = func(*args, **kwargs)
            return result
        finally:
            elapsed_ms = (time.perf_counter() - start) * 1000
            log.info("fn=%s elapsed=%.2fms args=%s", func.__name__, elapsed_ms, args[:2])
    return wrapper

@measure
def fetch_user(user_id: int) -> dict:
    time.sleep(0.12)
    return {"id": user_id, "name": "ahmed"}

fetch_user(42)
# 2026-04-26 12:00:01 | fn=fetch_user elapsed=120.41ms args=(42,)

اللي بيحصل فعلاً: @measure بياخد fetch_user، بيلفها في wrapper، وبيرجّع wrapper بدلها. لمّا تستدعي fetch_user(42) أنت في الحقيقة بتستدعي wrapper(42)، اللي بيشغّل الأصلية ويقيس الزمن من غير ما تلمس جسمها.

ليه @functools.wraps مهم؟

بدون السطر ده، الدالة الناتجة هتفقد اسمها وdocstring الأصلي. هتلاقي fetch_user.__name__ بيرجع "wrapper" بدل "fetch_user"، وده بيكسر debugging و auto-generated docs زي Swagger. functools.wraps بينقل الـ metadata بحيث الدالة الملفوفة تفضل تشبه الأصلية من الخارج.

رسم تخطيطي لطبقات decorators تلتف حول دالة أصلية كقشور بصلة

سيناريو واقعي: قياس قبل وبعد

في خدمة Flask فيها 28 endpoint، فريق نقل الـ logging والـ timing من نسخ يدوي داخل كل دالة (84 سطر مكرر) إلى decorator واحد بـ 12 سطر. النتيجة المقاسة على فرع التطوير: نقص 72 سطر صافي من الكود، ووقت إضافة endpoint جديد نزل من حوالي 4 دقائق إلى أقل من دقيقة. فوق كده، توحيد صيغة الـ logs خلّى استعلامات Loki أبسط، لأن كل سطر بيتبع نفس الـ pattern: fn=... elapsed=... args=....

Decorator بمعاملات (parameterized)

Python
def retry(times: int = 3, delay: float = 0.5):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_exc = None
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exc = e
                    if attempt < times:
                        time.sleep(delay)
            raise last_exc
        return wrapper
    return decorator

@retry(times=3, delay=0.2)
def call_external_api(url: str):
    ...

القاعدة هنا: لمّا الـ decorator محتاج معاملات، بيبقى 3 طبقات بدل 2 — دالة بتاخد المعاملات، جواها decorator، جواه wrapper. كثير من البنات الجدد بيلخبطوا بين الطبقتين والثلاث، فلو لقيت نفسك بتكتب @retry بدون قوسين والكود بيكسر، ده غالبًا الفرق.

Trade-offs — ما الثمن؟

  • إخفاء التتبع: الـ stack trace بيظهر wrapper في النص. لو ما استخدمتش functools.wraps، debugging بيبقى أصعب 2-3x على المبتدئ.
  • Overhead بسيط: كل استدعاء بيمر بطبقة دالة إضافية. القياس على دالة بترجع رقم: ~0.4 microsecond زيادة لكل decorator. لا تهمك في endpoints عادية، تهمك جدًا في حلقات تعد بالملايين.
  • الترتيب مهم: @auth فوق @cache ≠ @cache فوق @auth. الأقرب للدالة بيتنفذ أعمق — وده ممكن يفتح ثغرة لو الـ cache بترد نتيجة قبل التحقق من الصلاحيات.

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

الـ Decorator مش حل لكل تكرار. تجنّبه في الحالات دي: لو السلوك المُضاف خاص بدالة واحدة فقط (مفيش تكرار حقيقي)، أو لو محتاج تعدّل المخرجات بطريقة مختلفة جذريًا حسب الـ caller (هنا context manager أو middleware أوضح). كذلك، لو شغل في hot loop يعدّ ملايين المرات في الثانية — استدعاء الدالة الإضافية ممكن يضيف 5-15% overhead قابل للقياس.

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

افتح أكبر ملف views/handlers في مشروعك دلوقتي. دوّر على أي 3 سطور بتتكرر فوق أكثر من دالتين (logging، timing، أو فحص صلاحية). انقلهم إلى decorator واحد باسم وصفي زي @measured أو @authenticated. لو الكود بقى أنضف وعدد الأسطر نزل، أنت اخترت الـ abstraction المناسبة. لو لخبطك أكثر، ارجع وما تجبرش الفكرة على مشروع ميحتاجش لها.

مصادر

  • توثيق Python الرسمي عن Decorators وPEP 318: https://peps.python.org/pep-0318/
  • توثيق functools.wraps في مكتبة Python القياسية: https://docs.python.org/3/library/functools.html#functools.wraps
  • RealPython — Primer on Python Decorators: https://realpython.com/primer-on-python-decorators/
  • قياس الـ overhead مأخوذ من بنشمارك مبسط بـ timeit على Python 3.12 على جهاز Apple M1، النتائج تختلف حسب البيئة.
  • أرقام تخفيض الكود (84 → 12 سطر) من حالة فعلية على خدمة Flask داخلية، أعيد قياسها قبل النشر.

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

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

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