2.23. المُزخرِفات

إن المُزخرِف هو قطعة من البنية النحوية تغلّف دالة (أو تابعاً) داخل دالة أخرى. ويمكن للغلاف أن يضيف سلوكاً قبل الاستدعاء الأصلي وبعده، أو يستبدل القيمة المعادة، أو يرفق بيانات وصفية. والصيغة هي:

@wrapper
def f():
    ...

إن السطر @wrapper مكافئ لـ f = wrapper(f) -- فالدالة f تُبنى بشكل طبيعي، ثم تُسلَّم إلى wrapper، وتُعاد النتيجة إلى ربط الاسم f.

A function entering a "wrapper" box on the left and emerging on the right as a decorated function.

يأخذ المُزخرِف دالة ويعيد دالة جديدة.

2.23.1. مُزخرِفات التوابع المضمّنة

تأتي بعض المُزخرِفات مع Python وتُستخدم داخل أجسام الأصناف.

2.23.1.1. @property

يحوّل تابعاً إلى سمة محسوبة. يصل إليها المستدعي كما لو كانت سمة عادية (دون أقواس)، لكن تابعاً يُشغَّل عند كل قراءة:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

استخدمه عندما تحتاج سمة تبدو بسيطة إلى عملية حسابية صغيرة خلفها. وإذا كان العمل مكلِّفاً، فضّل تابعاً عادياً -- إذ لا يتوقع المستدعون أن تكون قراءات السمات بطيئة.

2.23.1.2. @classmethod

يعرّف تابعاً يتلقى الصنف كوسيطه الأول بدلاً من نسخة. ويُسمى الوسيط الأول اصطلاحاً cls:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

إن توابع الأصناف هي الطريقة المعيارية لتوفير بُناة بديلة -- دوال مصنع تعيد نسخة مبنية بطريقة غير افتراضية.

2.23.1.3. @staticmethod

يعرّف تابعاً لا يتلقى لا النسخة ولا الصنف -- فهو مجرد دالة عادية تقيم في فضاء أسماء الصنف لأسباب تنظيمية:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

استخدمه باعتدال؛ فإذا كانت دالة ما لا علاقة لها حقاً بحالة الصنف، فإن دالة عادية على مستوى الوحدة تكون عادةً أنظف.

2.23.2. كتابة مُزخرِف مخصص

المُزخرِف هو دالة تأخذ دالة وتعيد دالة. والصيغة الدنيا:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

المخرجات:

calling add

ينغلق wrapper على func ويُمرّر كل شيء إليه. وتتيح *args / **kwargs له العمل على أي دالة، وليس فقط الدوال ذات الوسيطين. هذا النمط هو أساس مُزخرِفات أكثر تفصيلاً (التوقيت، التخزين المؤقت، إعادة المحاولة عند الفشل) لكن الجوهر يبقى دائماً واحداً: خذ دالة، أعِد دالة.