المُزيّنات والإغلاقات
المُزيّنات هي واحدة من أكثر ميزات بايثون أناقة. تتيح لك "إضافة أشياء إضافية" للدوال دون تعديل الكود الأصلي — إضافة تسجيل، توقيت، فحوصات أذونات... العديد من الأطر والمكتبات تستخدم المُزيّنات بشكل مكثف. فهمها هو علامة فارقة مهمة في إتقان بايثون.
1. الإغلاقات
الإغلاقات هي أساس المُزيّنات. ببساطة: دالة داخلية تتذكر متغيرات من الدالة الخارجية، حتى بعد انتهاء تنفيذ الدالة الخارجية.
def make_multiplier(n):
"""أنشئ دالة ضرب"""
def multiplier(x):
return x * n # الدالة الداخلية تتذكر المتغير الخارجي 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). السكر النحوي يجعل الكود أنظف.
مثال: مُزيّن تسجيل (الصعوبة ⭐⭐)
def log_call(func):
"""سجّل استدعاءات الدوال"""
def wrapper(*args, **kwargs):
print(f"[LOG] Calling {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} returned {result}")
return result
return wrapper
@log_call
def add(a, b):
return a + b
@log_call
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(add(3, 5))
print(greet("Zhang San"))
المخرجات:
[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) # حافظ على معلومات الدالة الأصلية
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!
مثال: مُزيّن التوقيت (الصعوبة ⭐⭐⭐)
import time
from functools import wraps
def timer(func):
"""احسب وقت تنفيذ الدالة"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"[Timer] {func.__name__} took {elapsed:.4f} seconds")
return result
return wrapper
@timer
def slow_function():
total = 0
for i in range(1000000):
total += i
return total
result = slow_function()
print(f"Result: {result}")
المخرجات (يختلف التوقيت حسب الحاسوب):
[Timer] slow_function took 0.0452 seconds
Result: 499999500000
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))
حالات الاستخدام الشائعة
- التسجيل: سجل تلقائياً استدعاءات كل دالة وقيم الإرجاع.
- قياس الأداء: قس وقت تنفيذ الدالة لتحديد الاختناقات.
- فحوصات الأذونات: أطر الويب تستخدم مُزيّنات
@login_requiredللتحقق من تسجيل دخول المستخدمين. - التخزين المؤقت:
@functools.lru_cache()يخزن نتائج الدوال تلقائياً. - إعادة المحاولة: أعد المحاولة تلقائياً عدداً محدداً من المرات عند فشل طلب الشبكة.
❓ أسئلة شائعة
*args و kwargs ضروريان في المُزيّنات؟*args, kwargs لتمرير جميع الوسائط. إذا كنت تغلّف فقط دوالاً بتوقيع محدد، يمكنك تسمية معاملات محددة، لكن هذا أقل عمومية. المُزيّنات العامة تستخدم دائماً تقريباً *args, **kwargs.
س:ما العلاقة بين المُزيّنات والإغلاقات؟ ج:المُزيّنات مُنفّذة باستخدام الإغلاقات. الإغلاق هو "دالة تتذكر المتغيرات الخارجية." المُزيّنات تستفيد من الإغلاقات حتى تتمكن الدالة الداخلية (wrapper) من الوصول إلى معامل func المُمرّر من الدالة الخارجية.
س:ما هي المُزيّنات المفيدة التي تأتي مع بايثون؟ ج:@property (الوصول إلى السمات)، @staticmethod (الدوال الثابتة)، @classmethod (دوال الفئة)، @functools.lru_cache() (التخزين المؤقت)، @functools.wraps (الحفاظ على معلومات الدالة)، @dataclass (فئات البيانات). جميعها في المكتبة القياسية.📖 ملخص
- الإغلاق: دالة داخلية تتذكر متغيرات الدالة الخارجية، حتى بعد انتهاء تنفيذ الدالة الخارجية
- المُزيّن: دالة تأخذ دالة وتُعيد دالة؛ تستخدم السكر النحوي
@ @wraps(func)يحافظ على__name__و__doc__الأصليين للدالة — استخدمه دائماً في المُزيّنات- المُزيّنات المُعَلمة: ثلاثة مستويات من التداخل — الخارجي يستقبل المعاملات، الأوسط يستقبل الدالة، الداخلي يعالج المنطق
- المُزيّنات المتعددة تُطبّق من الأسفل إلى الأعلى
- المُزيّنات هي "دوال الدوال" — تعزيز الدوال دون تعديل الكود الأصلي
📝 تمارين
-
مبتدئ (الصعوبة ⭐): اكتب مُزيّناً
print_callيطبع "جاري استدعاء دالة xxx" عند تنفيذ الدالة. -
متوسط (الصعوبة ⭐⭐): اكتب مُزيّناً
retry(max_attempts=3)يُعيد المحاولة تلقائياً العدد المحدد من المرات عندما تُثير الدالة استثناءً. إذا استنفدت المحاولات، ارفع الاستثناء النهائي. تلميح: استخدم حلقة for + try-except في الدالة الداخلية (wrapper). -
متقدم (الصعوبة ⭐⭐⭐): اكتب مُزيّناً مُعَلمًا
cache_result(ttl=60)يخبّئ قيمة إرجاع الدالة. خلال ثواني TTL، استدعاء بنفس المعاملات يُرجع النتيجة المخبأة؛ بعد TTL، أعد حساب القيمة.



