Python: المُزيّنات والإغلاقات
آخر تحديث: 2026-09-22
المُزيّنات هي واحدة من أكثر ميزات بايثون أناقة. تتيح لك "إضافة أشياء إضافية" للدوال دون تعديل الكود الأصلي — إضافة تسجيل، توقيت، فحوصات أذونات... العديد من الأطر والمكتبات تستخدم المُزيّنات بشكل مكثف. فهمها هو علامة فارقة مهمة في إتقان بايثون.
1. الإغلاقات
الإغلاقات هي أساس المُزيّنات. ببساطة: دالة داخلية تتذكر متغيرات من الدالة الخارجية، حتى بعد انتهاء تنفيذ الدالة الخارجية.
def make_multiplier(n):
"""أنشئ دالة ضرب"""
def multiplier(x):
return x * n # function الداخلية تتذكر variable الخارجي n
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
print(double(10)) # 20
على الرغم من أن double و triple تأتيان من نفس دالة المصنع make_multiplier، كل منهما تتذكر قيماً مختلفة لـ n (2 و 3). هذا هو الإغلاق — دالة + المتغيرات الخارجية التي تتذكرها.
2. أساسيات المُزيّنات
المُزيّن هو دالة تأخذ دالة كوسيطة وتُعيد دالة جديدة:
def my_decorator(func):
def wrapper():
print("=== Before function execution ===")
func()
print("=== After function execution ===")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
المخرجات:
=== Before function execution ===
Hello!
=== After function execution ===
@my_decorator تعادل say_hello = my_decorator(say_hello). السكر النحوي يجعل الكود أنظف.
▶ مثال: مُزيّن تسجيل (الصعوبة ⭐⭐)
▶ جرّب الكودالمخرجات:
[LOG] Calling add((3, 5), {})
[LOG] add returned 8
8
[LOG] Calling greet(('Zhang San',), {'greeting': 'Hello'})
[LOG] greet returned Hello, Zhang San!
3. functools.wraps: الحفاظ على معلومات الدالة الأصلية
المُزيّنات "تستبدل" الدالة الأصلية، مما يتسبب في فقدان اسم الدالة وسلسلة التوثيق. @wraps يصلح هذا:
from functools import wraps
def my_decorator(func):
@wraps(func) # حافظ على معلومات function الأصلية
def wrapper(*args, **kwargs):
"""دالة المُزيّن الداخلية"""
print("Before function execution...")
return func(*args, **kwargs)
return wrapper
@my_decorator
def say_hello():
"""دالة ترحيب بسيطة"""
print("Hello!")
print(say_hello.__name__) # say_hello (بدون @wraps، سيظهر wrapper)
print(say_hello.__doc__) # دالة ترحيب بسيطة (بدون @wraps، سيظهر دالة المُزيّن الداخلية)
@wraps عند كتابة المُزيّنات. بدونها، يُفقد اسم الدالة المُزيّنة وتوثيقها، مما يجعل التصحيح صعباً. استورد wraps من functools وطبّق @wraps(func) على الدالة الداخلية (wrapper).
4. المُزيّنات المُعَلمة
أحياناً المُزيّن نفسه يحتاج معاملات:
from functools import wraps
def repeat(times=2):
"""كرّر التنفيذ عدداً محدداً من المرات"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Zhang San")
المخرجات:
Hello, Zhang San!
Hello, Zhang San!
Hello, Zhang San!
▶ مثال: مُزيّن التوقيت (الصعوبة ⭐⭐⭐)
▶ جرّب الكودالمخرجات (يختلف التوقيت حسب الحاسوب):
[Timer] slow_function took 0.0452 seconds
Result: 499999500000
▶ مثال: مُزيّن توقيت بسيط (الصعوبة ⭐)
▶ جرّب الكودالمخرجات:
[sum_range] took 0.008123 seconds
Sum result: 5000050000
[greet] took 0.000001 seconds
Hello, Alice!
5. دمج مُزيّنات متعددة
يمكن للدالة أن تحتوي على مُزيّنات متعددة مكدسة. ترتيب التنفيذ من الأسفل إلى الأعلى:
from functools import wraps
def bold(func):
@wraps(func)
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def italic(func):
@wraps(func)
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@bold
@italic
def hello():
return "Hello"
print(hello()) # <b><i>Hello</i></b>
# ترتيب التنفيذ: أولاً @italic، ثم @bold
# تعادل: hello = bold(italic(hello))
6. حالات الاستخدام الشائعة
- التسجيل: سجل تلقائياً استدعاءات كل دالة وقيم الإرجاع.
- قياس الأداء: قس وقت تنفيذ الدالة لتحديد الاختناقات.
- فحوصات الأذونات: أطر الويب تستخدم مُزيّنات
@login_requiredللتحقق من تسجيل دخول المستخدمين. - التخزين المؤقت:
@functools.lru_cache()يخزن نتائج الدوال تلقائياً. - إعادة المحاولة: أعد المحاولة تلقائياً عدداً محدداً من المرات عند فشل طلب الشبكة.
❓ أسئلة شائعة
*args و الوسائط الكلمة المفتاحية المتنوعة (علامتا النجمتين) ضرورية حقاً في المُزيّنات؟*args ووسائط الكلمة المفتاحية المتنوعة لتمرير جميع الوسائط. إذا كنت تغلّف فقط دوالاً بتوقيع محدد، يمكنك تسمية معاملات محددة، لكن هذا أقل عمومية. المُزيّنات العامة تستخدم دائماً تقريباً هذا الشكل من فك التغليف.func المُمرّر من الدالة الخارجية.📖 ملخص
- الإغلاق: دالة داخلية تتذكر متغيرات الدالة الخارجية، حتى بعد انتهاء تنفيذ الدالة الخارجية
- المُزيّن: دالة تأخذ دالة وتُعيد دالة؛ تستخدم السكر النحوي
@ @wraps(func)يحافظ على__name__و__doc__الأصليين للدالة — استخدمه دائماً في المُزيّنات- المُزيّنات المُعَلمة: ثلاثة مستويات من التداخل — الخارجي يستقبل المعاملات، الأوسط يستقبل الدالة، الداخلي يعالج المنطق
- المُزيّنات المتعددة تُطبّق من الأسفل إلى الأعلى
- المُزيّنات هي "دوال الدوال" — تعزيز الدوال دون تعديل الكود الأصلي
📝 تمارين
-
مبتدئ (الصعوبة ⭐): اكتب مُزيّناً
print_callيطبع "جاري استدعاء دالة xxx" عند تنفيذ الدالة. -
متوسط (الصعوبة ⭐⭐): اكتب مُزيّناً
retry(max_attempts=3)يُعيد المحاولة تلقائياً العدد المحدد من المرات عندما تُثير الدالة استثناءً. إذا استنفدت المحاولات، ارفع الاستثناء النهائي. تلميح: استخدم حلقة for + try-except في الدالة الداخلية (wrapper). -
متقدم (الصعوبة ⭐⭐⭐): اكتب مُزيّناً مُعَلمًا
cache_result(ttl=60)يخبّئ قيمة إرجاع الدالة. خلال ثواني TTL، استدعاء بنفس المعاملات يُرجع النتيجة المخبأة؛ بعد TTL، أعد حساب القيمة.