تكرار السؤال: …
الإجابة المختصرة
اكتب دالة تستقبل الدالة الأصلية وتعيد wrapper، وطبّق functools.wraps على الـ wrapper لنسخ __name__ و __doc__ و __module__ و __wrapped__.
الشرح
بدون wraps ستظهر كل الدوال المزخرفة باسم wrapper في السجلات والـ tracebacks، وتفقد أدوات التوثيق والاختبار المعلومات الأصلية. الـ decorator الذي يقبل معاملات يحتاج طبقة إضافية: دالة تستقبل الإعدادات وتعيد الـ decorator نفسه. ولتحافظ على التوقيع لأدوات فحص الأنواع استخدم ParamSpec. اجعل الـ decorator صغيرًا وواضحًا، خصوصًا عند استخدامه للتسجيل أو الصلاحيات أو التخزين المؤقت.
مثال بالكود
import functools
import time
def retry(times: int):
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return fn(*args, **kwargs)
except ConnectionError:
if attempt == times:
raise
time.sleep(2 ** attempt)
return wrapper
return decorator
@retry(times=3)
def fetch_user(user_id: int): ...أخطاء شائعة
- نسيان functools.wraps فتضيع أسماء الدوال في السجلات.
- نسيان return داخل الـ wrapper فتعيد كل الدوال المزخرفة None.