المزخرف في بايثون: دالة تستقبل دالة وتعيد دالة
تكتب @do_twice فوق دالة تطبع كلمة واحدة، ثم تستدعيها مرة واحدة، فتظهر الكلمة على الشاشة مرتين. لم تلمس سطراً داخل الدالة، ومع ذلك تغيّر سلوكها.
يبدو هذا للوهلة الأولى ميزة خاصة في اللغة. لكن فكرة هذا المقال بسيطة: المزخرف في بايثون ليس آلية مستقلة، بل دالة عادية تستقبل دالة وتعيد دالة، والرمز @ اختصار لعملية إسناد لا أكثر. ومن يمسك بهذه الفكرة يفكّ أغلب ما يبدو غامضاً في الموضوع.

Source: Primer on Python Decorators – Real Python — realpython.com
الرمز @ إسناد مختصر
تعرّف منصة Coddy المزخرفات بأنها وسيلة لتعديل الدوال والأصناف أو تحسينها دون تغيير كودها الأصلي، وتشير إلى أنها تُكتب بالرمز @. وتصفها منصة easyenv بأنها غلاف صغير يوضع حول الدالة فيضيف إليها سلوكاً مثل التسجيل أو قياس الزمن أو فحص الصلاحية.
أما ما يحدث فعلاً عند كتابة ذلك الرمز فيشرحه دليل Real Python بمثال واضح:
def decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@decorator
def say_whee():
print("Whee!")
ويقرر الدليل أن @decorator ليست سوى طريقة أقصر لكتابة say_whee = decorator(say_whee). أي أن الاسم say_whee صار يشير إلى الدالة الداخلية wrapper لا إلى الدالة الأصلية.
ويقول موقع learnpython.org الشيء نفسه بعبارة أخرى: المزخرف دالة تأخذ دالة وتعيد دالة، والسطر الذي يسبقه @ يعادل تمرير الدالة إلى المزخرف ثم إعادة إسناد النتيجة إلى الاسم ذاته.
لأنه دالة عادية، تنطبق عليه أدوات الدوال
يترتب على ذلك أثر عملي يذكّر به دليل Real Python: ما دام المزخرف دالة عادية، فكل أدوات إعادة الاستخدام متاحة له. فيقترح الدليل جمع المزخرفات في ملف اسمه decorators.py، ثم استيرادها في ملفات أخرى كما يُستورد أي شيء:
from decorators import do_twice
@do_twice
def say_whee():
print("Whee!")
والمزخرف do_twice نفسه لا يزيد على دالة داخلية تستدعي الدالة الممرَّرة مرتين. ويقدّم learnpython.org مثالاً مطابقاً تقريباً باسم repeater، ويعلّق بأن الدالة الجديدة يجب أن تُعاد، وإلا لما أُسندت إلى الاسم.
ويعرض Real Python كذلك قالباً عاماً يستقبل أي وسائط ويمررها، ويعيد قيمة الدالة الأصلية، ويستخدم functools.wraps، ويصفه بأنه نموذج صالح لبناء مزخرفات أعقد. وعلى هذا القالب يبني الدليل مزخرف @timer الذي يقيس زمن تنفيذ الدالة ويطبعه.
حتى المزخرف ذو المعاملات يتبع القاعدة نفسها
قد يبدو @multiply(3) خروجاً عن القاعدة، فهنا استدعاء بعد الرمز @ لا اسم مجرد. لكن المثال الذي يقدمه learnpython.org يكشف أنه طبقة إضافية فقط: الدالة multiply تستقبل المضاعِف وتعيد مزخرفاً، وهذا المزخرف هو الذي يستقبل الدالة.
ويعلّق الموقع داخل الكود بأن multiply ليست هي المولّد، بل multiply(3) هي المولّد. والنتيجة أن الدالة return_num بعد زخرفتها تعيد 15 عند استدعائها بالرقم 5.
ويضيف الموقع ملاحظة تؤكد الفكرة من جهة أخرى: يمكنك أن تفعل بالدالة القديمة ما تشاء، بل أن تتجاهلها كلياً. فالمزخرف لا يعدّل الدالة في مكانها، وإنما يقرر ما الذي سيحمله الاسم بعد الإسناد.
ما الذي يفتحه هذا الفهم
تفتتح easyenv درسها بوصف المزخرفات بأنها من أصعب مواضيع بايثون. وقراءتي، وهي استنتاج لا تقوله المصادر صراحة، أن جزءاً كبيراً من هذه الصعوبة مصدره الرمز @ نفسه، لأنه يخفي سطر الإسناد الذي يشرح كل شيء. فإذا أعيدت كتابة المزخرف على صورة f = decorator(f) تراجع الغموض.
لكن هذا الفهم يطرح سؤالاً عملياً: إذا صار الاسم يشير إلى دالة أخرى، فما الذي يضيع من الدالة الأصلية؟ يلمّح learnpython.org إلى أن المزخرفات المتقدمة قد تتعامل مع نص التوثيق وعدد الوسائط، ويضع Real Python functools.wraps في قالبه الأساسي. غير أن المقتطفات المتاحة هنا لا تشرح بالتفصيل ما تحفظه wraps، ولا كيف تُزخرف الأصناف التي ذكرتها Coddy، وهما موضوعان يحتاج كل منهما إلى مقال مستقل.
