اكتشف كيف تحول الديكوريترز في بايثون الكود العادي إلى آلات ذكية تتعامل مع الكاشينغ، المصادقة، والـ Logging دون تعديل الوظائف الأصلية. أمثلة متقدمة تكشف أسرار الأداء والذاكرة خلف الكواليس.
تخيل أنك تعمل على مشروع ضخم فيه مئات الـ Endpoints في Flask أو FastAPI، وكل واحد يحتاج مصادقة، تسجيل دخول، وكاشينغ. بدلاً من تكرار نفس الكود في كل وظيفة، تخيل لو أن الوظائف نفسها تستطيع أن تلبس عباءة ذكية تضيف هذه الميزات تلقائياً. هذا ليس خيالاً، بل هو بالضبط ما يفعله الـ Decorator في بايثون. لكن المشكلة الحقيقية تبدأ عندما تكتشف أن الديكوريترز ليست مجرد سكر سينتاكسي، بل هي آلية قوية تتحكم في تنفيذ الكود، الذاكرة، وحتى الـ Event Loop في التطبيقات الآسنكرونية. إذا استخدمت بشكل خاطئ، ستجد نفسك أمام سيرفر بيعلق، أو ما هو أسوأ: Memory Leak لا يمكن تتبعه.
الحقيقة هي أن معظم المطورين يستخدمون الديكوريترز كطريقة لتجنب تكرار الكود، لكنهم لا يفهمون حقاً كيف تعمل تحت الغطاء. عندما تنادي بـ `my_function()` بعد تطبيق ديكوريتر، فإن بايثون لا تنادي بالوظيفة الأصلية مباشرة، بل تمرر التحكم إلى الديكوريتر أولاً، الذي بدوره قد يعدل المدخلات، التنفيذ، أو المخرجات. هذا يعني أن الديكوريتر يمكن أن يكون سلاحاً ذا حدين: إما أن يسرع تطبيقك بعشر مرات باستخدام الكاشينغ، أو يبطئه بسبب الـ Overhead غير المرئي. في هذا المقال، سنفكك الديكوريترز من الصفر حتى نصل إلى الاستخدامات المتقدمة التي يستخدمها المحترفون في شركات مثل Google وNetflix لتحسين الأداء وتقليل الـ Boilerplate.
عندما تكتب `@decorator` فوق وظيفة، فإن بايثون تقوم بتحويل هذا السطر إلى عملية استدعاء لوظيفة أخرى. لكن ما يحدث بالضبط في الذاكرة؟ دعنا نقول لدينا هذا الكود البسيط:
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
return func(*args, **kwargs)
return wrapper
@logger
def add(a, b):
return a + b
print(add(3, 5))عندما تصرح بـ `@logger` فوق `add`، فإن بايثون تفعل شيئاً غير متوقع: إنها تستبدل المتغير `add` بنتيجة استدعاء `logger(add)`. هذا يعني أن `add` لم يعد يشير إلى الوظيفة الأصلية، بل إلى الـ `wrapper` الذي داخل الديكوريتر. وعندما تنادي بـ `add(3, 5)`، فإنك في الواقع تنادي بالـ `wrapper` الذي بدوره ينادي بالوظيفة الأصلية. هذا السلوك يفسر لماذا تفقد الوظيفة المزينة بعض خصائصها مثل اسم الدالة (`__name__`) ودوك سترينغ (`__doc__`)، لأن الـ `wrapper` هو الذي أصبح الوظيفة الجديدة في نظر بايثون.
هذه الآلية تسمى بالـ First-Class Functions، وهي أحد أقوى ميزات بايثون. الديكوريتر ليس مجرد سينتاكس جميل، بل هو تطبيق عملي لهذه الميزة. لكن المشكلة تظهر عندما تريد الوصول إلى الوظيفة الأصلية بعد تطبيق الديكوريتر. مثلاً، إذا حاولت استخدام `inspect.getsource(add)` بعد تطبيق الديكوريتر، ستحصل على كود الـ `wrapper` بدلاً من كود `add` الأصلي. هذا يمكن أن يسبب مشاكل في الـ Debugging أو حتى في الـ Unit Testing إذا لم تكن حذراً.
في المشاريع الحقيقية، نادراً ما تستخدم ديكوريتر واحداً فقط. مثلاً، قد تريد وظيفة تقوم بالكاشينغ وتسجيل الدخول في نفس الوقت. هنا يأتي دور الديكوريترز المتداخلة، لكن يجب أن تكون حذراً جداً في ترتيبها. انظر إلى هذا المثال:
import time
def cache(func):
stored = {}
def wrapper(*args):
if args in stored:
print("Returning cached result")
return stored[args]
result = func(*args)
stored[args] = result
return result
return wrapper
def log_execution_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@log_execution_time
@cache
def slow_function(x):
time.sleep(2) # محاكاة عملية بطيئة
return x * x
print(slow_function(4)) # الأولى بطيئة
print(slow_function(4)) # الثانية سريعة من الكاش