🔒

سجّل للوصول إلى الدروس

للاستمرار في تعلم بايثون، يرجى إنشاء حساب مجاني أو تسجيل الدخول إلى حسابك الحالي.

وصول مجاني لجميع دروس بايثون
حفظ تقدمك تلقائياً
بيئة تجربة بايثون التفاعلية
الدرس 15

المُزخرفات (Decorators)

تعلم كيفية تعديل وتحسين الدوال دون تغيير كودها الأصلي

📚 الوحدة 4: الدوال ⏱️ 18 دقيقة قراءة

ما هي المُزخرفات؟

المُزخرفات هي دوال تُعدّل أو تُحسّن دوالاً أخرى دون تغيير كودها. تُغلّف الدالة لإضافة وظائف قبل أو بعد تنفيذها.

Python فهم المُزخرفات خطوة بخطوة
# الخطوة 1: الدوال يمكن تمريرها كوسيطات
def تحية():
    return "مرحباً!"

def استدعاء_دالة(دالة):
    return دالة()

print(استدعاء_دالة(تحية))  # مرحباً!

# الخطوة 2: الدوال يمكن أن تُرجع دوالاً
def إنشاء_تحية(الاسم):
    def تحية():
        return f"مرحباً يا {الاسم}!"
    return تحية

قل_مرحباً = إنشاء_تحية("أحمد")
print(قل_مرحباً())  # مرحباً يا أحمد!

# الخطوة 3: مُزخرف بسيط
def مزخرفي(دالة):
    def مُغلّف():
        print("قبل استدعاء الدالة")
        الناتج = دالة()
        print("بعد استدعاء الدالة")
        return الناتج
    return مُغلّف

def قل_مرحباً():
    print("مرحباً!")

# التزخرف اليدوي
مُزخرَفة = مزخرفي(قل_مرحباً)
مُزخرَفة()
# قبل استدعاء الدالة
# مرحباً!
# بعد استدعاء الدالة

صيغة @

بايثون توفر صيغة أنظف لتطبيق المُزخرفات:

Python استخدام صيغة @
# استخدام صيغة @ (سكر نحوي)
def مزخرفي(دالة):
    def مُغلّف():
        print("قبل")
        الناتج = دالة()
        print("بعد")
        return الناتج
    return مُغلّف

@مزخرفي  # نفس: قل_مرحباً = مزخرفي(قل_مرحباً)
def قل_مرحباً():
    print("مرحباً!")

قل_مرحباً()
# قبل
# مرحباً!
# بعد

# هذا مكافئ لـ:
# قل_مرحباً = مزخرفي(قل_مرحباً)

المُزخرفات مع الوسيطات

Python التعامل مع وسيطات الدالة
# التعامل مع وسيطات الدالة بـ *args, **kwargs
def تسجيل_استدعاء(دالة):
    def مُغلّف(*args, **kwargs):
        print(f"استدعاء {دالة.__name__}")
        print(f"  Args: {args}")
        print(f"  Kwargs: {kwargs}")
        الناتج = دالة(*args, **kwargs)
        print(f"  النتيجة: {الناتج}")
        return الناتج
    return مُغلّف

@تسجيل_استدعاء
def جمع(أ, ب):
    return أ + ب

@تسجيل_استدعاء
def تحية(الاسم, رسالة_ترحيب="مرحباً"):
    return f"{رسالة_ترحيب} يا {الاسم}!"

جمع(3, 5)
# استدعاء جمع
#   Args: (3, 5)
#   Kwargs: {}
#   النتيجة: 8

تحية("أحمد", رسالة_ترحيب="أهلاً")
# استدعاء تحية
#   Args: ('أحمد',)
#   Kwargs: {'رسالة_ترحيب': 'أهلاً'}
#   النتيجة: أهلاً يا أحمد!

الحفاظ على بيانات الدالة الأصلية

Python استخدام @wraps
from functools import wraps

# بدون wraps - البيانات الوصفية تُفقد
def مزخرف_سيء(دالة):
    def مُغلّف(*args, **kwargs):
        return دالة(*args, **kwargs)
    return مُغلّف

@مزخرف_سيء
def دالتي():
    """النص التوثيقي لدالتي."""
    pass

print(دالتي.__name__)  # مُغلّف (خطأ!)
print(دالتي.__doc__)   # None (ضاع!)

# مع wraps - البيانات الوصفية محفوظة
def مزخرف_جيد(دالة):
    @wraps(دالة)  # يحافظ على البيانات الوصفية
    def مُغلّف(*args, **kwargs):
        return دالة(*args, **kwargs)
    return مُغلّف

@مزخرف_جيد
def دالتي():
    """النص التوثيقي لدالتي."""
    pass

print(دالتي.__name__)  # دالتي (صحيح!)
print(دالتي.__doc__)   # النص التوثيقي لدالتي.
💡 أفضل ممارسة

استخدم دائماً @wraps(دالة) من functools في مُزخرفاتك للحفاظ على البيانات الوصفية للدالة الأصلية (الاسم، النص التوثيقي، إلخ).

أمثلة عملية على المُزخرفات

مُزخرف قياس الوقت

Python قياس وقت التنفيذ
import time
from functools import wraps

def مؤقت(دالة):
    """قياس وقت تنفيذ الدالة."""
    @wraps(دالة)
    def مُغلّف(*args, **kwargs):
        البداية = time.time()
        الناتج = دالة(*args, **kwargs)
        النهاية = time.time()
        print(f"{دالة.__name__} استغرقت {النهاية - البداية:.4f} ثانية")
        return الناتج
    return مُغلّف

@مؤقت
def دالة_بطيئة():
    time.sleep(1)
    return "تم"

الناتج = دالة_بطيئة()
# دالة_بطيئة استغرقت 1.0012 ثانية

مُزخرف إعادة المحاولة

Python إعادة المحاولة عند الفشل
import time
from functools import wraps

def إعادة_محاولة(الحد_الأقصى=3, التأخير=1):
    """إعادة محاولة الدالة عند الفشل."""
    def مُزخرِف(دالة):
        @wraps(دالة)
        def مُغلّف(*args, **kwargs):
            المحاولات = 0
            while المحاولات < الحد_الأقصى:
                try:
                    return دالة(*args, **kwargs)
                except Exception as e:
                    المحاولات += 1
                    print(f"المحاولة {المحاولات} فشلت: {e}")
                    if المحاولات < الحد_الأقصى:
                        time.sleep(التأخير)
            raise Exception(f"فشلت بعد {الحد_الأقصى} محاولات")
        return مُغلّف
    return مُزخرِف

@إعادة_محاولة(الحد_الأقصى=3, التأخير=0.5)
def عملية_غير_موثوقة():
    import random
    if random.random() < 0.7:  # 70% احتمال الفشل
        raise ValueError("فشل عشوائي!")
    return "نجاح!"

# ستحاول حتى 3 مرات
try:
    الناتج = عملية_غير_موثوقة()
    print(الناتج)
except Exception as e:
    print(e)

مُزخرف التخزين المؤقت

Python تخزين نتائج الدالة مؤقتاً
from functools import wraps

def تخزين_مؤقت(دالة):
    """تخزين نتائج الدالة مؤقتاً."""
    الذاكرة = {}

    @wraps(دالة)
    def مُغلّف(*args):
        if args not in الذاكرة:
            الذاكرة[args] = دالة(*args)
        return الذاكرة[args]
    return مُغلّف

@تخزين_مؤقت
def فيبوناتشي(ن):
    """حساب رقم فيبوناتشي (بطيء بدون تخزين مؤقت)."""
    if ن < 2:
        return ن
    return فيبوناتشي(ن - 1) + فيبوناتشي(ن - 2)

# بدون تخزين مؤقت، فيبوناتشي(35) ستأخذ وقتاً طويلاً
# مع تخزين مؤقت، فورية
print(فيبوناتشي(35))  # 9227465

# بايثون لديها تخزين مؤقت مُدمج: lru_cache
from functools import lru_cache

@lru_cache(maxsize=128)
def فيب(ن):
    if ن < 2:
        return ن
    return فيب(ن - 1) + فيب(ن - 2)

print(فيب(100))  # فوري!

مُزخرف التحقق

Python التحقق من صحة المعاملات
from functools import wraps

def تحقق_موجب(*أسماء_المعاملات):
    """التأكد من أن المعاملات المحددة أرقام موجبة."""
    def مُزخرِف(دالة):
        @wraps(دالة)
        def مُغلّف(*args, **kwargs):
            # الحصول على أسماء معاملات الدالة
            import inspect
            التوقيع = inspect.signature(دالة)
            المعاملات = list(التوقيع.parameters.keys())

            # فحص الوسيطات الموضعية
            for i, (معامل, قيمة) in enumerate(zip(المعاملات, args)):
                if معامل in أسماء_المعاملات:
                    if not isinstance(قيمة, (int, float)) or قيمة <= 0:
                        raise ValueError(f"{معامل} يجب أن يكون موجباً، حصلنا على {قيمة}")

            # فحص الوسيطات المُسماة
            for معامل in أسماء_المعاملات:
                if معامل in kwargs:
                    قيمة = kwargs[معامل]
                    if not isinstance(قيمة, (int, float)) or قيمة <= 0:
                        raise ValueError(f"{معامل} يجب أن يكون موجباً، حصلنا على {قيمة}")

            return دالة(*args, **kwargs)
        return مُغلّف
    return مُزخرِف

@تحقق_موجب('العرض', 'الارتفاع')
def حساب_المساحة(العرض, الارتفاع):
    return العرض * الارتفاع

print(حساب_المساحة(5, 3))      # 15
# print(حساب_المساحة(-5, 3))   # ValueError: العرض يجب أن يكون موجباً

المُزخرفات مع معاملات

Python مُزخرفات قابلة للتخصيص
from functools import wraps

# مصنع مُزخرفات - يُرجع مُزخرفاً
def تكرار(المرات):
    """تكرار تنفيذ الدالة ن مرة."""
    def مُزخرِف(دالة):
        @wraps(دالة)
        def مُغلّف(*args, **kwargs):
            النتائج = []
            for _ in range(المرات):
                النتائج.append(دالة(*args, **kwargs))
            return النتائج
        return مُغلّف
    return مُزخرِف

@تكرار(المرات=3)
def تحية(الاسم):
    return f"مرحباً يا {الاسم}!"

print(تحية("أحمد"))
# ['مرحباً يا أحمد!', 'مرحباً يا أحمد!', 'مرحباً يا أحمد!']

# مُزخرف التحكم في الصلاحيات
def يتطلب_دور(الدور):
    """التحقق من أن المستخدم لديه الدور المطلوب."""
    def مُزخرِف(دالة):
        @wraps(دالة)
        def مُغلّف(المستخدم, *args, **kwargs):
            if المستخدم.get('الدور') != الدور:
                raise PermissionError(f"يتطلب دور {الدور}")
            return دالة(المستخدم, *args, **kwargs)
        return مُغلّف
    return مُزخرِف

@يتطلب_دور('مدير')
def حذف_مستخدم(المستخدم, معرف_المستخدم):
    return f"تم حذف المستخدم {معرف_المستخدم} بواسطة {المستخدم['الاسم']}"

مدير = {'الاسم': 'أحمد', 'الدور': 'مدير'}
عادي = {'الاسم': 'سارة', 'الدور': 'مستخدم'}

print(حذف_مستخدم(مدير, 123))  # تم حذف المستخدم 123 بواسطة أحمد
# حذف_مستخدم(عادي, 123)      # PermissionError

تسلسل المُزخرفات المتعددة

Python تطبيق مُزخرفات متعددة
from functools import wraps

def عريض(دالة):
    @wraps(دالة)
    def مُغلّف(*args, **kwargs):
        return f"{دالة(*args, **kwargs)}"
    return مُغلّف

def مائل(دالة):
    @wraps(دالة)
    def مُغلّف(*args, **kwargs):
        return f"{دالة(*args, **kwargs)}"
    return مُغلّف

def مسطر(دالة):
    @wraps(دالة)
    def مُغلّف(*args, **kwargs):
        return f"{دالة(*args, **kwargs)}"
    return مُغلّف

# المُزخرفات تُطبق من الأسفل للأعلى
@عريض
@مائل
@مسطر
def تحية(الاسم):
    return f"مرحباً يا {الاسم}"

print(تحية("أحمد"))
# مرحباً يا أحمد

# مكافئ لـ:
# تحية = عريض(مائل(مسطر(تحية)))

المُزخرفات المبنية على الفئات

Python مُزخرف كفئة
class عداد_الاستدعاءات:
    """عد عدد مرات استدعاء الدالة."""

    def __init__(self, دالة):
        self.دالة = دالة
        self.العدد = 0

    def __call__(self, *args, **kwargs):
        self.العدد += 1
        print(f"الاستدعاء #{self.العدد} لـ {self.دالة.__name__}")
        return self.دالة(*args, **kwargs)

@عداد_الاستدعاءات
def قل_مرحباً():
    print("مرحباً!")

قل_مرحباً()  # الاستدعاء #1 لـ قل_مرحباً \n مرحباً!
قل_مرحباً()  # الاستدعاء #2 لـ قل_مرحباً \n مرحباً!
قل_مرحباً()  # الاستدعاء #3 لـ قل_مرحباً \n مرحباً!

print(f"إجمالي الاستدعاءات: {قل_مرحباً.العدد}")  # إجمالي الاستدعاءات: 3

# مُزخرف فئة مع معاملات
class تكرار:
    def __init__(self, المرات):
        self.المرات = المرات

    def __call__(self, دالة):
        def مُغلّف(*args, **kwargs):
            for _ in range(self.المرات):
                الناتج = دالة(*args, **kwargs)
            return الناتج
        return مُغلّف

@تكرار(المرات=3)
def تحية(الاسم):
    print(f"مرحباً يا {الاسم}!")

تحية("سارة")
# مرحباً يا سارة!
# مرحباً يا سارة!
# مرحباً يا سارة!

🎯 تحدي: مُحدد معدل الاستدعاءات

أنشئ مُزخرفاً يحد من عدد مرات استدعاء الدالة:

  • يقبل معامل استدعاءات_في_الثانية
  • إذا استُدعيت بشكل متكرر جداً، ينتظر قبل التنفيذ
  • يتتبع وقت آخر استدعاء
💡 عرض الحل
import time
from functools import wraps

def محدد_المعدل(استدعاءات_في_الثانية):
    """تحديد استدعاءات الدالة إلى ن مرة في الثانية."""
    الفترة_الدنيا = 1.0 / استدعاءات_في_الثانية

    def مُزخرِف(دالة):
        آخر_استدعاء = [0]  # استخدام قائمة للسماح بالتعديل

        @wraps(دالة)
        def مُغلّف(*args, **kwargs):
            المنقضي = time.time() - آخر_استدعاء[0]
            if المنقضي < الفترة_الدنيا:
                وقت_الانتظار = الفترة_الدنيا - المنقضي
                print(f"محدود المعدل. الانتظار {وقت_الانتظار:.2f} ثانية...")
                time.sleep(وقت_الانتظار)

            آخر_استدعاء[0] = time.time()
            return دالة(*args, **kwargs)
        return مُغلّف
    return مُزخرِف

@محدد_المعدل(استدعاءات_في_الثانية=2)  # حد أقصى استدعاءين في الثانية
def استدعاء_api(النقطة):
    print(f"استدعاء {النقطة}")
    return f"استجابة من {النقطة}"

# اختبار - سيحد المعدل
for i in range(5):
    استدعاء_api(f"/api/مورد/{i}")

🎯 تحدي: مُزخرف التصحيح

أنشئ مُزخرف تصحيح:

  • يطبع اسم الدالة والوسيطات والقيمة المُرجعة
  • يُظهر وقت التنفيذ
  • يمكن تمكينه/تعطيله بعلامة
💡 عرض الحل
import time
from functools import wraps

التصحيح = True  # علامة التصحيح العامة

def تصحيح(دالة):
    """مُزخرف تصحيح مع علامة تمكين/تعطيل."""
    @wraps(دالة)
    def مُغلّف(*args, **kwargs):
        if not التصحيح:
            return دالة(*args, **kwargs)

        # بناء نص الوسيطات
        نص_args = [repr(أ) for أ in args]
        نص_kwargs = [f"{م}={ق!r}" for م, ق in kwargs.items()]
        التوقيع = ", ".join(نص_args + نص_kwargs)

        print(f"استدعاء {دالة.__name__}({التوقيع})")

        البداية = time.time()
        try:
            الناتج = دالة(*args, **kwargs)
            المدة = time.time() - البداية
            print(f"  -> {الناتج!r} ({المدة:.4f} ثانية)")
            return الناتج
        except Exception as e:
            المدة = time.time() - البداية
            print(f"  -> استثناء: {e!r} ({المدة:.4f} ثانية)")
            raise
    return مُغلّف

@تصحيح
def حساب(أ, ب, العملية="جمع"):
    if العملية == "جمع":
        return أ + ب
    elif العملية == "ضرب":
        return أ * ب
    else:
        raise ValueError(f"عملية غير معروفة: {العملية}")

# اختبار مع التصحيح مُفعل
حساب(5, 3)
حساب(5, 3, العملية="ضرب")

# تعطيل التصحيح
التصحيح = False
حساب(5, 3)  # لا مخرجات تصحيح

📝 ملخص الدرس

  • المُزخرفات تُعدّل الدوال دون تغيير كودها
  • استخدم صيغة @مُزخرِف فوق تعريف الدالة
  • استخدم دائماً @wraps(دالة) للحفاظ على البيانات الوصفية
  • استخدم *args, **kwargs للتعامل مع أي توقيع دالة
  • المُزخرف مع معاملات يحتاج ثلاث دوال متداخلة
  • المُزخرفات المتعددة تُطبق من الأسفل للأعلى
  • الاستخدامات الشائعة: التسجيل، التوقيت، التخزين المؤقت، التحقق، التحكم في الصلاحيات

في الدرس التالي، سنتعلم عن المولدات - مُكررات موفرة للذاكرة!