نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
المقالات/Python
Python

Python Decorators: عندما تصبح الوظائف مجرد قطع ليغو في يد المهندس

اكتشف كيف تحولDecorators في بايثون الكود من تكرار ممل إلى فن برمجي، مع أمثلة عملية تتجاوز الأساسيات لتصل إلى تقنيات تستخدمها شركات مثل جوجل وريديت في الإنتاج.

فريق نوفيل١٦ يوليو ٢٠٢٦8 دقائق قراءة١٢ مشاهدة

تخيل أنك تعمل على واجهة برمجية لمعالجة الصور في شركة ناشئة، وكل مكالمة لـ API تحتاج إلى تسجيل الوقت، التحقق من الصلاحيات، وتخزين النتيجة في قاعدة البيانات. تكتب نفس الكود ثلاث مرات، ثم تكتشف أن أحد المطورين نسى إضافة التحقق في نقطة واحدة. النتيجة؟ ثغرة أمنية تسمح للمستخدمين غير المصرح لهم بمعالجة صور حساسة. هنا بالضبط تظهر قوة Python Decorators - إنها ليست مجرد أداة لتنظيم الكود، بل هي طريقة لإعادة التفكير في كيفية بناء الوظائف نفسها.

في عالم بايثون، الـ Decorators هي أكثر من مجرد سكر نحوي. إنها آلية تسمح لك بتعديل سلوك الوظائف أو الكلاسات دون تغيير كودها الأصلي. فكر فيها كطبقة شفافة تلتف حول الوظيفة الأصلية، تضيف إليها سلوكاً جديداً دون أن تلمس محتواها. هذا المفهوم ليس مجرد ترفيهي - شركات مثل ريديت تستخدم Decorators لإدارة الـ rate limiting في واجهات برمجتها، بينما تستخدمها جوجل في مكتبات مثل TensorFlow لإضافة سلوكيات مثل الـ logging والتحقق من المدخلات دون تكرار الكود.

ما يحدث خلف الكواليس: الذاكرة والمعالج عندما تزين وظيفة

عندما تكتب @retry فوق وظيفة، بايثون لا تضيف مجرد سطر جميل فوق التعريف. إنها تنفذ عملية معقدة في الذاكرة. أولاً، تُنشئ بايثون كائناً جديداً من نوع function يمثل الـ decorator نفسه. ثم تمرر الوظيفة الأصلية (التي تريد تزيينها) كوسيط لهذا الكائن. داخل الـ decorator، تُنشئ بايثون وظيفة جديدة مغلفة (wrapper) تلتقط كلاً من الـ arguments والـ keyword arguments الأصلية. هذا الـ wrapper هو الذي سيُستدعى فعلياً عند استدعاء الوظيفة المزينة، وليس الوظيفة الأصلية.

المثير للاهتمام هنا هو أن بايثون تحتفظ بمرجع للوظيفة الأصلية عبر closure. هذا يعني أن الـ wrapper يمكنها الوصول إلى المتغيرات المحلية في نطاق الـ decorator حتى بعد انتهاء تنفيذه. إذا كنت تستخدم متغيرات مثل max_retries داخل الـ decorator، فسيتم الاحتفاظ بقيمتها في الذاكرة طالما أن الوظيفة المزينة موجودة. هذا السلوك هو ما يسمح للـ Decorators بأن تكون ديناميكية وقابلة لإعادة الاستخدام في نفس الوقت، لكنه أيضاً ما قد يسبب تسريبات للذاكرة إذا لم تكن حذراً مع المتغيرات التي تلتقطها الـ closures.

python
import time
from functools import wraps

def log_execution_time(func):
 @wraps(func) # يحافظ على metadata الوظيفة الأصلية
 def wrapper(*args, **kwargs):
 start_time = time.perf_counter()
 result = func(*args, **kwargs) # استدعاء الوظيفة الأصلية
 end_time = time.perf_counter()
 print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
 return result
 return wrapper

@log_execution_time
def process_large_dataset(data):
 # محاكاة معالجة بيانات تستغرق وقتاً
 time.sleep(2)
 return sum(x * 2 for x in data if x > 100)

# عند الاستدعاء، wrapper هي التي تُستدعى بالفعل
result = process_large_dataset(range(10000))

الـ Decorators المتداخلة: عندما تصبح الوظيفة مثل البصلة

في المشاريع الكبيرة، غالباً ما تحتاج إلى تطبيق أكثر من decorator على نفس الوظيفة. مثلاً، قد تحتاج إلى تسجيل الوقت، التحقق من الصلاحيات، والتعامل مع الأخطاء في نفس الوقت. هنا تأتي قوة الـ nested decorators. بايثون تطبق الـ decorators من الأسفل إلى الأعلى، لكن التنفيذ الفعلي يحدث من الأعلى إلى الأسفل. هذا يعني أن الـ decorator الأقرب إلى تعريف الوظيفة هو الأول في التنفيذ، والأبعد هو الأخير.

المشكلة الشائعة هنا هي فقدان metadata الوظيفة الأصلية. عندما تطبق عدة decorators، قد ينتهي بك الأمر بوظيفة لا تعرف اسمها أو docstring الأصلي. هذا هو سبب أهمية استخدام @wraps من وحدة functools - فهي تحافظ على هوية الوظيفة الأصلية حتى بعد تطبيق عدة طبقات من الـ decorators. بدونها، ستجد أن debuggers وأدوات مثل help() ستبدأ بإظهار معلومات خاطئة عن وظيفتك، مما يجعل عملية التطوير أكثر صعوبة بكثير.

python
from functools import wraps

def authorize(role_required):
 def decorator(func):
 @wraps(func)
 def wrapper(*args, **kwargs):
 user_role = kwargs.get('user_role', 'guest')
 if user_role != role_required:
 raise PermissionError(f"Role {role_required} required")
 return func(*args, **kwargs)
 return wrapper
 return decorator

def cache_response(timeout=60):
 cache = {}
 def decorator(func):
 @wraps(func)
 def wrapper(*args, **kwargs):
 cache_key = (args, frozenset(kwargs.items()))
 if cache_key in cache:
 result, timestamp = cache[cache_key]
 if time.time() - timestamp < timeout:
 return result
 result = func(*args, **kwargs)
 cache[cache_key] = (result, time.time())
 return result
 return wrapper
 return decorator

@cache_response(timeout=300)
@authorize('admin')
def get_sensitive_data(user_id, user_role='guest'):
 """Retrieve sensitive data from database"""
 print(f"Fetching data for user {user_id}")
 return {"data": f"Sensitive info for {user_id}"}

# التنفيذ: cache_response -> authorize -> الوظيفة الأصلية
# لكن عند الاستدعاء: authorize أولاً ثم cache_response

الـ Decorators مع الكلاسات: عندما تصبح self مجرد متغير آخر

العديد من المطورين يعتقدون أن الـ Decorators تقتصر على الوظائف، لكن الحقيقة هي أنها تعمل بنفس الفعالية مع الكلاسات والطرق. عندما تزين طريقة في كلاس، فإن الـ decorator يتلقى self كوسيط أول، تماماً مثل أي طريقة عادية. هذا يعني أنه يمكنك تعديل سلوك الطرق بناءً على حالة الكائن، أو حتى إضافة طرق جديدة ديناميكياً إلى الكلاس.

المثال الكلاسيكي هنا هو إضافة سلوكيات مثل الـ logging أو الـ validation إلى الطرق دون تغيير الكود الأصلي. لكن الاستخدامات الأكثر تقدماً تشمل تعديل سلوك الكلاس بالكامل بناءً على ظروف التشغيل. مثلاً، يمكنك استخدام decorator لتحويل كلاس عادي إلى كلاس singleton بضربة واحدة، أو لإضافة طرق جديدة إلى الكلاس بناءً على الإعدادات في وقت التشغيل. هذا النوع من المرونة هو ما يجعل بايثون لغة مثالية للمشاريع التي تحتاج إلى تعديل سلوكها ديناميكياً دون إعادة كتابة الكود.

python
def singleton(cls):
 instances = {}
 @wraps(cls)
 def wrapper(*args, **kwargs):
 if cls not in instances:
 instances[cls] = cls(*args, **kwargs)
 return instances[cls]
 return wrapper

@singleton
def get_db_connection():
 print("Creating new DB connection")
 return {"connection": "live"}

# كل استدعاء يرجع نفس الكائن
c get_db_connection()
conn2 = get_db_connection()
print(conn1 is conn2) # True

# مع الكلاسات
@singleton
class Database:
 def __init__(self, url):
 self.url = url
 print(f"Initializing database at {url}")

# نفس الكائن يُعاد في كل مرة
db1 = Database("postgres://localhost")
db2 = Database("postgres://another")
print(db1 is db2) # True
print(db1.url) # postgres://localhost

الـ Decorators مع الـ Descriptors: عندما تصبح الكلاسات أكثر ذكاءً

عندما تجمع بين الـ Decorators و الـ Descriptors، تحصل على قوة هائلة في التحكم في سلوك الكلاسات. الـ Descriptors هي آلية بايثون تسمح لك بتخصيص ما يحدث عند الوصول إلى سمات الكلاس (get, set, delete). عندما تستخدم decorator لتعديل طريقة تعمل كـ descriptor، يمكنك تغيير سلوك الوصول إلى السمات ديناميكياً بناءً على ظروف معينة.

المثال العملي هنا هو إضافة سلوكيات مثل الـ lazy loading أو الـ caching إلى سمات الكلاس. بدلاً من تحميل البيانات الثقيلة عند إنشاء الكائن، يمكنك استخدام decorator لجعل السمة تُحمل فقط عند أول وصول إليها. هذا النوع من الأنماط يُستخدم بكثرة في أطر العمل مثل Django و SQLAlchemy لتحسين أداء التطبيقات الكبيرة.

python
class LazyProperty:
 def __init__(self, func):
 self.func = func
 self.attr_name = f"_{func.__name__}"
 
 def __get__(self, instance, owner):
 if instance is None:
 return self
 if not hasattr(instance, self.attr_name):
 setattr(instance, self.attr_name, self.func(instance))
 return getattr(instance, self.attr_name)

def lazy_load(func):
 return LazyProperty(func)

class UserProfile:
 def __init__(self, user_id):
 self.user_id = user_id
 
 @lazy_load
 def posts(self):
 print("Loading posts from database...")
 return [f"Post {i}" for i in range(100)]
 
 @lazy_load
 def friends(self):
 print("Loading friends from API...")
 return [f"Friend {i}" for i in range(50)]

profile = UserProfile(1)
print("Profile created")
# لم تُحمل البيانات بعد
print(profile.posts[0]) # الآن تُحمل البيانات
print(profile.friends) # تُحمل بيانات الأصدقاء

الـ Decorators في الإنتاج: الفخاخ التي يقع فيها حتى المحترفون

عندما تنتقل من الأمثلة البسيطة إلى الاستخدام في الإنتاج، ستواجه مشاكل حقيقية لا تغطيها معظم الدروس. أحد أكبر هذه المشاكل هو تسرب الذاكرة بسبب الـ closures. عندما يلتقط الـ decorator متغيرات من النطاق الخارجي، تبقى هذه المتغيرات في الذاكرة طالما أن الوظيفة المزينة موجودة. إذا كنت تستخدم متغيرات كبيرة داخل الـ decorator، فقد ينتهي بك الأمر باستهلاك ذاكرة هائلة دون أن تدري.

مشكلة أخرى شائعة هي الأداء. كل decorator تضيفه يضيف طبقة إضافية من الاستدعاءات، وهذا يعني زيادة في وقت التنفيذ. في التطبيقات الحساسة للأداء، قد تلاحظ أن الـ decorators تضيف تكلفة غير مقبولة. الحل هنا هو استخدام أدوات مثل cProfile لقياس التأثير الحقيقي، وأحياناً استبدال الـ decorators البسيطة بكود مباشر في النقاط الحرجة من التطبيق.

  • •فقدان الـ metadata: بدون @wraps، تفقد معلومات مثل __name__ و __doc__، مما يجعل الـ debugging صعباً
  • •الـ side effects غير المتوقعة: إذا كان الـ decorator يعدل الـ arguments أو الـ return value، قد تحصل على سلوك غير متوقع في أجزاء أخرى من الكود
  • •الـ thread safety: إذا كان الـ decorator يستخدم متغيرات مشتركة بين الخيوط، قد تواجه مشاكل في التزامن
  • •الـ recursion: استخدام الـ decorators مع الوظائف العودية قد يؤدي إلى سلوك غير متوقع بسبب الـ wrapping المتكرر
  • •الـ pickling: بعض الـ decorators تجعل الكائنات غير قابلة للتسلسل باستخدام pickle بسبب الـ closures

مثال واقعي: كيف تستخدم ريديت الـ Decorators لإدارة الـ Rate Limiting

إحدى الاستخدامات العملية الأكثر شيوعاً للـ Decorators في الشركات الكبيرة هي إدارة الـ rate limiting في واجهات البرمجة. ريديت، على سبيل المثال، تستخدم نظاماً مشابهاً للتحكم في عدد الطلبات التي يمكن للمستخدم إرسالها في فترة زمنية معينة. الفكرة بسيطة: بدلاً من كتابة كود التحقق في كل نقطة نهاية، تستخدم decorator واحدة تُطبق على جميع الـ endpoints التي تحتاج إلى هذا السلوك.

الميزة الحقيقية هنا هي المرونة. يمكنك تعديل سلوك الـ rate limiting في مكان واحد دون الحاجة إلى تغيير كل نقطة نهاية. بالإضافة إلى ذلك، يمكنك تطبيق مستويات مختلفة من القيود بناءً على نوع المستخدم أو نوع الطلب. مثلاً، المستخدمون العاديون قد يكون لديهم حد 60 طلب في الدقيقة، بينما المستخدمون المدفوعون قد يحصلون على 1000 طلب في الدقيقة. كل هذا يمكن التحكم فيه من خلال وسيط يُمرر للـ decorator عند تعريفه.

python
import time
from functools import wraps
from collections import defaultdict

# تخزين عدد الطلبات لكل مستخدم
request_counts = defaultdict(list)

def rate_limit(max_requests, period):
 def decorator(func):
 @wraps(func)
 def wrapper(user_id, *args, **kwargs):
 now = time.time()
 # إزالة الطلبات القديمة
 request_counts[user_id] = [
 t for t in request_counts[user_id] 
 if now - t < period
 ]
 
 if len(request_counts[user_id]) >= max_requests:
 oldest_request = request_counts[user_id][0]
 wait_time = period - (now - oldest_request)
 raise Exception(f"Rate limit exceeded. Try again in {wait_time:.1f} seconds")
 
 request_counts[user_id].append(now)
 return func(user_id, *args, **kwargs)
 return wrapper
 return decorator

@rate_limit(max_requests=5, period=60)
def get_user_posts(user_id):
 """Get posts for a specific user"""
 return [f"Post {i} by {user_id}" for i in range(10)]

# اختبار الـ rate limiting
for i in range(6):
 try:
 print(f"Request {i+1}: {get_user_posts('user123')[:2]}...")
 except Exception as e:
 print(f"Request {i+1}: {e}")

متى يجب تجنب الـ Decorators: نصيحة المهندس المخضرم

على الرغم من قوة الـ Decorators، هناك حالات يجب فيها تجنب استخدامها تماماً. أول هذه الحالات هي عندما يكون الكود حساساً للأداء بشكل كبير. كل decorator تضيفه يضيف طبقة إضافية من الاستدعاءات، وهذا يعني زيادة في وقت التنفيذ. في التطبيقات التي تحتاج إلى استجابة فورية مثل الألعاب أو أنظمة التداول عالية التردد، قد تكون هذه التكلفة غير مقبولة.

الحالة الثانية هي عندما يكون الكود بحاجة إلى شفافية كاملة. الـ Decorators تجعل سلوك الوظائف أقل وضوحاً، خاصة للمطورين الجدد في الفريق. إذا كان عليك شرح كيفية عمل وظيفة معينة لعدة دقائق، فقد يكون من الأفضل كتابة الكود بشكل مباشر بدلاً من استخدام decorator معقد. تذكر أن الهدف من البرمجة هو التواصل مع البشر أولاً، والآلات ثانياً.

أخيراً، تجنب استخدام الـ Decorators عندما يكون السلوك الذي تريد إضافته فريداً جداً ولا يُستخدم في أماكن أخرى. إذا كنت تكتب decorator يُستخدم مرة واحدة فقط، فأنت تضيف تعقيداً دون أي فائدة حقيقية. في هذه الحالة، يكون من الأفضل كتابة الكود مباشرة داخل الوظيفة نفسها. القاعدة البسيطة هنا هي: إذا لم يكن الكود قابلاً لإعادة الاستخدام في ثلاثة أماكن مختلفة على الأقل، فلا تجعله decorator.

خلاصة المهندس: سطران يغيران طريقة تفكيرك في بايثون

الـ Decorators في بايثون ليست مجرد أداة لتنظيم الكود - إنها طريقة لإعادة التفكير في كيفية بناء البرامج. عندما تفهم حقاً كيف تعمل الـ closures و الـ first-class functions، ستدرك أن الوظائف في بايثون ليست مجرد كتل من الكود، بل هي كائنات حية يمكن تعديلها وتوسيعها ديناميكياً. هذه العقلية هي ما يميز المطورين الذين يكتبون كوداً نظيفاً وقابلاً للصيانة عن أولئك الذين يكتبون كوداً يعمل فحسب.

نصيحة عملية أخيرة: ابدأ دائماً بكتابة الـ decorator كدالة عادية تستقبل الوظيفة كوسيط، ثم حولها إلى صيغة @ بعد أن تتأكد من أنها تعمل بشكل صحيح. هذه الطريقة تجعل عملية الـ debugging أسهل بكثير، وتجنبك الكثير من الصداع عندما لا يعمل الكود كما تتوقع. وعندما تصبح مرتاحاً مع الأساسيات، انتقل إلى الاستخدامات المتقدمة مثل الـ nested decorators والـ decorators مع الكلاسات. بهذه الطريقة، ستتحول من مطور يستخدم الـ decorators إلى مهندس يفهم حقاً كيف تعمل بايثون تحت الغطاء.

بايثون متقدمة برمجة وظيفية هندسة البرمجيات أنماط تصميم أداء بايثون

التعليقات

العودة للمقالات
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر