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

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

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

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

المنصة

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

الدعم

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

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

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

Python Decorators: أضف Logging و Caching بدون تعديل الدالة

📅 ١٩ أبريل ٢٠٢٦⏱ 4 دقائق قراءة
Python Decorators: أضف Logging و Caching بدون تعديل الدالة

Python Decorators: إزاي تضيف سلوك لأي دالة بدون ما تلمسها

لو لقيت نفسك بتنسخ نفس الـ 5 سطور بتاعة الـ logging في 20 فانكشن، الـ decorators هتخلّيك تكتبها مرة واحدة بس. نفس المبدأ بيشتغل على caching وauthorization وقياس الزمن. الكلام مش نظري: هتلاقي في المقال decorator جاهز تنسخه تشغّله دلوقتي.

شاشة تعرض كود Python يستخدم بنية decorator مع علامة at للف الدوال

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

عندك 15 دالة، كل واحدة محتاجة تطبع اسمها وقت الاستدعاء ومدّة التنفيذ. لو حطيت نفس الـ time.perf_counter() والـ log.info في كل واحدة، خلقت 15 نقطة تعديل. لو حبيت تغيّر format الـ log بكرة، هتفتح 15 ملف. Decorator واحد بيلف الدالة ويحقن السلوك ده مرة واحدة بس من غير ما تمس جسم الدالة.

الـ Decorator ببساطة: مفيش سحر

الـ decorator ببساطة دالة بتاخد دالة، وترجّع نسخة ملفوفة منها. Python بيسهّل الصياغة بعلامة @ فوق تعريف الدالة. ركز: ده مجرد higher-order function، مش feature غامض.

Python
def my_decorator(func):
    def wrapper(*args, **kwargs):
        # قبل الاستدعاء
        result = func(*args, **kwargs)
        # بعد الاستدعاء
        return result
    return wrapper

@my_decorator
def greet(name):
    return f"Hello {name}"

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

ده decorator جاهز تنسخه. بيسجّل اسم الدالة، arguments، والمدة بالميللي ثانية حتى لو الدالة رمت exception.

Python
import functools
import time
import logging

logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)

def log_and_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            return func(*args, **kwargs)
        finally:
            elapsed_ms = (time.perf_counter() - start) * 1000
            log.info(
                "call func=%s args=%s kwargs=%s elapsed_ms=%.2f",
                func.__name__, args, kwargs, elapsed_ms,
            )
    return wrapper

@log_and_time
def fetch_user(user_id: int):
    time.sleep(0.05)
    return {"id": user_id, "name": "Ahmed"}

السطر @functools.wraps(func) مش اختياري. بدونه fetch_user.__name__ هيرجع "wrapper" بدل الاسم الأصلي، وده بيكسر أي monitoring بيعتمد على اسم الدالة زي Sentry و OpenTelemetry.

Caching جاهز من المكتبة: lru_cache

Python جاي ومعاه decorator جاهز للـ memoization اسمه functools.lru_cache. بيحفظ نتيجة كل استدعاء حسب الـ arguments، فلو نديت نفس الدالة بنفس الـ input تاني، بيرجّع من الكاش بدون ما يشغّل الجسم.

محرر كود يعرض دالة مزخرفة بـ functools.lru_cache لتسريع fibonacci
Python
from functools import lru_cache

@lru_cache(maxsize=256)
def fib(n: int) -> int:
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

# قياس فعلي على MacBook M1 / Python 3.12:
# fib(35) بدون cache ≈ 2800 ms
# fib(35) مع cache  ≈ 0.3 ms

المكسب: تسريع تقريبًا × 9000 على المثال ده. التكلفة: استهلاك ذاكرة بيزيد مع كل نتيجة محفوظة، لحد maxsize. الافتراض إن الـ input hashable؛ لو بتبعت list أو dict، هيرمي TypeError.

الـ Trade-offs اللي لازم تعرفها

  • Stack traces أطول. كل decorator بيضيف frame، والـ exceptions بتبقى أصعب قراءة شوية.
  • Debugging أبطأ. الـ breakpoint جوا الدالة الأصلية ممكن يتعدى لو الـ wrapper بيعمل early return.
  • Overhead. كل استدعاء بيمر على الـ wrapper. في دالة بتتندّه ملايين المرات في الثانية، ده بيفرق. قيس قبل ما تقرر.
  • Thread safety. lru_cache thread-safe بشكل افتراضي، لكن decorator مكتوب بإيدك مش بالضرورة.

متى لا تستخدم هذه الطريقة

لو السلوك اللي عايز تضيفه محتاج يوصل لـ self بتاع الكلاس بشكل معقّد، غالبًا descriptor أو mixin أنضف من decorator. ولو الدالة بترجع generator أو async، الـ wrapper لازم يتعامل مع ده بشكل صريح (async def wrapper مع await). الافتراض الضمني فوق إن الكود synchronous عادي.

تحذير مهم: ممنوع تستخدم lru_cache على دالة بترجع object قابل للتعديل (mutable) زي dict أو list. لو حد عدّل الـ dict المرجَع، النسخة المحفوظة نفسها بتتغير، وأي استدعاء بعد كده بيرجع النسخة المعدّلة. ده bug بطيء الاكتشاف جدًا في production.

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

افتح ملف Python فيه أكتر من دالة بتنسخ فيها نفس الـ logging أو الـ timing. انسخ log_and_time من فوق، شيل الكود المكرر من جسم الدوال، وشغّل. لو شفت في الـ logs سطر elapsed_ms على كل call من غير ما تعدّل الجسم، يبقى الـ decorator مركّب صح.

المصادر

  • Python Docs — Glossary: Decorator — https://docs.python.org/3/glossary.html#term-decorator
  • Python Docs — functools (lru_cache, wraps) — https://docs.python.org/3/library/functools.html
  • PEP 318 — Decorators for Functions and Methods — https://peps.python.org/pep-0318/
  • Real Python — Primer on Python Decorators — https://realpython.com/primer-on-python-decorators/

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

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

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