Decorators — Python المتقدم
المزخرفات (Decorators)
المزخرف دالة تأخذ دالة وترجع نسخة معدّلة منها. تستخدمها لإضافة سلوك مشترك (قياس الوقت، التسجيل، التحقق من الصلاحيات، التخزين المؤقت) لعدة دوال دون تكرار الكود داخل كل منها. يعتمد على ما تعلمته: الدوال كقيم والإغلاقات.
الفكرة بدون صيغة @
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"استدعاء {func.__name__} بـ {args}")
return func(*args, **kwargs)
return wrapper
def add(a, b):
return a + b
add = log_calls(add) # استبدلنا add بالنسخة المغلّفة
add(2, 3)صيغة @ هي اختصار لنفس السطر
@log_calls
def add(a, b):
return a + bتعني حرفياً: add = log_calls(add).
لماذا *args و **kwargs؟
لكي يعمل الـ wrapper مع أي دالة مهما كانت معاملاتها: *args تجمع المعاملات الموضعية في tuple، و **kwargs تجمع المعاملات بالاسم في قاموس، ثم نمررها كما هي للدالة الأصلية. ولا تنسَ return نتيجة الدالة الأصلية وإلا سترجع الدالة المزخرفة None.
functools.wraps
بدونها تفقد الدالة اسمها وتوثيقها (add.__name__ يصبح "wrapper")، مما يربك رسائل الأخطاء وأدوات الاختبار:
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return wrapperاجعلها عادة في كل مزخرف.
مزخرف بمعاملات
مستوى إضافي من الدوال: دالة تأخذ الإعدادات وترجع المزخرف:
def repeat(times):
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 hello():
print("مرحباً")مزخرفات جاهزة مفيدة
| المزخرف | الوظيفة |
|---|---|
| @functools.lru_cache | يحفظ نتائج الاستدعاءات السابقة (ممتاز للدوال البطيئة النقية) |
| @property، @classmethod، @staticmethod | رأيتها في دورة OOP |
| @dataclass | يولّد دوال الصنف |
ترتيب عدة مزخرفات
def a(func):
print("a تُطبَّق ثانياً")
return func
def b(func):
print("b تُطبَّق أولاً")
return func
@a
@b
def f(): ...تساوي f = a(b(f)): الأقرب للدالة يُطبَّق أولاً.
تمرين
اكتب مزخرف @timer يطبع زمن تنفيذ الدالة، ومزخرف @retry(times) يعيد المحاولة إذا رمت الدالة استثناءً.
# Decorator بسيط
def my_decorator(func):
def wrapper():
print("قبل الدالة")
func()
print("بعد الدالة")
return wrapper
@my_decorator
def say_hello():
print("مرحباً!")
say_hello()
# قبل الدالة
# مرحباً!
# بعد الدالة
# Decorator مع معاملات
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"مرحباً {name}!")
greet("أحمد")
# Timer Decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} استغرق {end - start:.4f} ثانية")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "انتهى!"
print(slow_function())
# @property - جعل method تبدو كـ attribute
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("لا يمكن أن يكون نصف القطر سالباً")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(circle.radius) # 5
print(circle.area) # 78.53975
circle.radius = 10
print(circle.area) # 314.159اختبر فهمك