python 101 المزخرفات (Decorators) في بايثون


عبدالهادي الديوري

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

python-decorators.png

تذكير ببعض المفاهيم الأساسية

إذا لم تكن تعرف شيئا عن الدوال في لغة بايثون فيجب عليك العودة للدرس السابق الدوال في بايثون قبل أن تكمل قراءة هذا الدرس. 

تُعتبر الدوال في لغة بايثون كائنات من نوع الفئة الأولى أو First class objects. ما يعني أنّنا نستطيع القيام بالعديد من العمليات، وهي كالتالي:

  • يُمكنك تعريف دالة داخل دالة أخرى
  • يُمكنك أن تستدعي دالة داخل أخرى
  • يُمكنك أن تقوم بتمرير دالة كمُعامل لدالة أخرى
  • يُمكنك أن تُسند دالة لمُتغير
  • يُمكنك إرجاع دالة داخل دالة أخرى

بما أنّ المُزخرفات مُجرّد دوال فعلينا أن نبدأ من الأساس، لاحظ الدالة التالية:

def say_hello():
    print 'Hello!'

عند استدعاء الدالة ستُطبع القيمة Hello على الشاشة، هذا ليس بالأمر المُعقد، الآن ماذا لو أردنا أن نعدل الدالة في جزء آخر من البرنامج لكي تطبع أشياء أخرى وتؤدي أغراضا أخرى قبل استدعاء الدالة أو بعد ذلك؟ يُمكن أن نعدّل الدالة مُباشرة، لكن هذا الأمر سيغير من طريقة سير البرنامج، إذ نريد أن نعدل الدالة في منطقة واحدة فقط من البرنامج وليس في كامل البرنامج، هذه المسألة تُحل بالمُزخرفات، وكما يدل اسمها فهي دوال تُزيّن وتُزخرف الدالة الأصلية، أي تُضيف عليها مهاما أخرى.

سننشئ لهذه الدالة الآن مُزخرفا Decorator يقوم بطباعة Before قبل تنفيذ الدالة و After بعد تنفيذ الدالة، وذلك دون تعديل الدالة مُباشرة. انظر المثال التالي:

def decorator(function):
    def function_decorator():
        print 'Before'
        function()
        print 'After'
    return function_decorator

الشيفرة أعلاه عبارة عن دالة تقبل دالة أخرى (الدالة التي نرغب بزَخرَفَتِها أو تزيينها) كمُعامل وبعدها نقوم بإنشاء دالة داخل هذه الدالة لطباعة القيمة Before ثم استدعاء الدالة الأصلية (المُعامل) بعدها طباعة After وأخيرا إرجاع الدالة الجديدة (وهي نُسخة مزخرفة من الدالة الأصلية). بعدها يُمكننا أن نستخدم هذا المُزخرف لزخرفة أي دالة مهما كانت، انظر المثال التالي:

 # -*- coding: utf-8 -*-  
def decorator(function):      # إنشاء الدالة المسؤولة عن الزخرفة
    def function_decorator():    # إنشاء الدالة التي ستكون نسخة مزخرفة من الدالة المُمرّرة في كمُعامل
        print 'Before' # طباعة جملة قبل تنفيذ الدالة
        function()     # استدعاء الدالة
        print 'After'
    return function_decorator # إرجاع الدالة مُزَخْرَفَةً
def say_hello(): # إنشاء دالة عادية
    print 'Hello!'
say_hello = decorator(say_hello) # زخرفة الدالة
say_hello() # استدعاء النُسخة المُزخرفة من الدالة

توفر لنا لغة بايثون طريقة أكثر مرونة لزخرفة الدوال، وهي بوضع اسم المُزخرف مسبوقا بالحرف @ قبل تعريف الدالة. أي أنّ السّطر التالي:

def say_hello(): 
    print 'Hello!'
say_hello = decorator(say_hello) # زخرفة الدالة

يُمكن أن يكون كالتالي:

@decorator
def say_hello(): # إنشاء دالة عادية
    print 'Hello!'

وبالتالي سنتمكن من زخرفة أي دالة نرغب بزخرفتها كالتالي:

@decorator
def say_hello(): 
    print 'Hello!'
@decorator
def say_hi(): 
    print 'Hi!'
@decorator
def say_name(): 
    print 'Abdelhadi!'
say_hello()
say_hi()
say_name()

عند تنفيذ الشيفرة أعلاه ستكون المخرجات كالتالي:

Before
Hello!
After

Before
Hi!
After

Before
Abdelhadi!
After

مُلاحظة: إذا كانت للدالة معاملات فما عليك إلا استخدام args* التي سبق وتحدثنا عنها في الدرس السابق.

 # -*- coding: utf-8 -*-  
def decorator(function):      # إنشاء الدالة المسؤولة عن الزخرفة
    def function_decorator(*args):  # إنشاء الدالة التي ستكون نسخة مزخرفة من الدالة المُمرّرة كمُعامل
        print 'Before' # طباعة جملة قبل تنفيذ الدالة
        function(*args)     # استدعاء الدالة
        print 'After'
    return function_decorator # إرجاع الدالة مُزَخْرَفَةً

لاحظ الدالتين function_decorator و function.

أمثلة على المزخرفات في لغة بايثون

إذا فهمت مبدأ المزخرفات فستستطيع أن تتعامل مع الدوال بمرونة عالية، وإليك بعض الأمثلة لاستخدام المُزخرفات لتأدية بعض المهام البسيطة:

حساب مدة تنفيذ دالة

إذا فهمت جيدا مبدأ المُزخرفات فستلاحظ بأنّك تستطيع تنفيذ مهام قبل تنفيذ الدالة ومهام بعد تنفيذها، ومما سبق نستنتج بأنّنا نستطيع أن نقوم بحفظ الوقت الحالي في مُتغير ثم تنفيذ الدالة وبعدها نقوم بحساب الفرق بين الوقت السابق والوقت الحالي، ما سيُرجع المُدة المُستغرقة لتنفيذ الدالة. البرنامج التالي مثال على مُزخرف لحساب مُدة دالة تطبع الجملة !Hello World مليون مرّة.

# -*- coding: utf-8 -*-
import time # جلب مكتبة الوقت لاستعمال دوال الوقت
def function_time(function):
    def function_decorator(*args):
        start_time = time.time() # الحصول على وقت البداية
        function(*args)
        end_time = time.time() # الحصول على الوقت بعد نهاية التنفيذ
		# طباعة اسم الدالة والفرق بين وقت البداية ووقت النهاية 
        print '%s function took %0.3f s' % (function.func_name, (end_time - start_time)) 

    return function_decorator # إرجاع الدالة مُزَخْرَفَةً
# زخرفة الدالة المسؤولة عن الطباعة مليون مرة
@function_time
def print_million_times():
    for i in range(0, 1000000):
        print 'Hello World! 1,000,000 times!'

print_million_times()

البرنامج أعلاه سيطبع الجملة مليون مرة ثم يعرض الوقت المُستغرق لإنجاز العملية. الجملة الأخيرة ستكون شيئا كالتّالي:

print_million_times function took 69.584 s

ملاحظات:

  • نستعمل التابع func_name للحصول على اسم الدالة المُمررة كمعامل، ويكون على شكل سلسلة نصية. 
  • نستعمل الجملة time.time للحصول على الوقت بالثواني، عدد الثواني الذي تنتجه الجملة هو عدد الثواني الذي مرّ منذ سنة 1970. 
  • يُمكنك استعمال هذا المُزخرف مع أي دالة تريد فقط اكتب اسم المُزخرف مسبوقا بالحرف @ ثم عرف الدالة بعده، وستحصل على الوقت المُستغرق لتنفيذ الدالة.

حساب عدد مرات استدعاء دالة

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

# -*- coding: utf-8 -*-
# متغير العد
n = 0
# المُزخرف
def call_times(function):
    def decorated():
        function() # استدعاء الدالة
        global n   # جعل مُتغير العدّ عالميا
        n += 1     # زيادة قيمة المُتغير
        print 'Function was called', n, 'times' # طباعة قيمة المُتغير
    return decorated

@call_times # زخرفة الدالة
def func(): # إنشاء الدالة
    print 'Hello!'
# استدعاء الدالة
func()
func()
func()
func()

مُخرجات البرنامج أعلاه ستكون كالتالي:

Hello!
Function was called 1 times
Hello!
Function was called 2 times
Hello!
Function was called 3 times
Hello!
Function was called 4 times

يُمكنك إصلاح الجمل من الناحية اللغوية بإضافة بعض العبارات الشرطية للبرنامج.

إنشاء مزخرف لتنفيذ دالة عند تحقق شرط معين فقط

يُمكنك أن تستعمل دالة تسجيل الدخول التي قُمنا بإنشائها كمُزخرف للدوال التي تحتاج لأن يكون المُستخدم مُسجلا دخوله. مثلا لنقل بأنّنا نريد أن نعرض على المُستخدم عدة خيارات بعضها يحتاج إلى تسجيل دخول المُستخدم وبعضها لا. 

الخيارات كالتّالي: 

  1. تسجيل مُستخدم جديد (تسجيل الدخول غير مطلوب) 
  2. طباعة جملة عشر مرات ( تسجيل الدخول غير مطلوب) 
  3. الحصول على الوقت الحالي ( تسجيل الدخول غير مطلوب) 
  4. طباعة اسم المُستخدم (تسجيل الدخول مطلوب) 
  5. رؤية معلومات الحساب (تسجيل الدخول مطلوب) 
  6. تعديل كلمة المرور (تسجيل الدخول مطلوب)

مبدأ عمل البرنامج سيكون كالتالي: 

  1. إنشاء الدوال المسؤولة عن الخيارات 
  2. عرض الخيارات على المُستخدم 
  3. زخرفة الدوال التي تطلب تسجيل المُستخدم بمُزخرف تسجيل الدخول 
  4. المُزخرف سيتحقق من أنّ المُستخدم قد سجل دخوله، إذا كان الأمر كذلك، تنفّذ الدالة وإذا لم يتحقق الشرط فلا تنفذ.

لنقل بأنّ اسم مُزخرف التحقق من تسجيل الدخول هو is_user_logged_in، ستكون الدوال التي تطلب تسجيل الدخول مُزَخْرَفَةً كالتالي:

@if_user_logged_in
def account_info():
    print 'Username:', username, 'Password:', password

تمارين

تمرين 1

أنشئ دالة للجمع بين عددين، وبعدها أنشئ مُزخرفا يقوم بمُضاعفة النتيجة.

تمرين 2

أنشئ دالة للحصول على قيم من المُستخدم وقم بزخرفة لطباعة جملة ترحيب قبل استدعاء الدالة وجملة توديع بعد استدعاءها.

تمرين 3

أكمل البرنامج الخاص بالمثال الثالث (إنشاء مُزخرف لتنفيذ دالة عند تحقق شرط مُعين فقط). تفاصيل التمرين موجودة بالمثال.



2 اشخاص أعجبوا بهذا


تفاعل الأعضاء


جزاك الله خيرا

صراحة أنا لم افهم اللغة الا منك , أتمنى أن تنشر مقالات لشرح OOP في اقرب وقت , لانها جزء غامض بالنسبة لي حتى الان ,

شارك هذا التعليق


رابط هذا التعليق
شارك على الشبكات الإجتماعية

جزيت خيرًا

أعتقد أنّه من الأفضل استخدام

function.__name__

لاستدعاء اسم الدالة بدلًا من

function.func_name

ألا ترى معي ذلك؟

شكرًا

1 شخص أعجب بهذا

شارك هذا التعليق


رابط هذا التعليق
شارك على الشبكات الإجتماعية


يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن