ديكوريترز بايثون: كيفية إنشاء واستخدام المزيّنات في Python مع أمثلة عملية

دقائق القراءة: 7

ما هي ديكوريترز Python ولماذا تُعد مهمة؟

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

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

شرح ديكوريترز بايثون وكيفية إنشاء المزيّنات واستخدامها في تطوير التطبيقات

متى تستخدم ديكوريتر في Python؟

استخدم الديكوريتر عندما ترغب في تغيير سلوك دالة من الخارج بدلاً من تعديل جسم الدالة نفسه. هذا الخيار مثالي في الحالات التالية:

  • إضافة سجلات تنفيذ باستخدام logging.
  • قياس السرعة واستهلاك الذاكرة أو الزمن.
  • التحقق من صلاحيات المستخدم قبل تنفيذ وظيفة معينة.
  • تطبيق التخزين المؤقت cache على نتائج الدوال.
  • تنفيذ منطق مشترك على عدد كبير من الدوال دون تكرار الشيفرة.

إذا وجدت نفسك تكتب الأسطر نفسها قبل كل دالة أو بعدها، فغالباً أنت بحاجة إلى ديكوريتر.

الأساسيات التي يجب فهمها قبل إنشاء ديكوريتر

لفهم آلية عمل الديكوريترز بشكل سليم، من المهم استيعاب بعض المفاهيم الأساسية في Python.

الدالة كائن يمكن إسناده إلى متغير

في Python، الدالة تُعامل ككائن object، ولذلك يمكنك إسنادها إلى متغير ثم استدعاؤها لاحقاً عبر هذا المتغير.

def my_function (
):
    print('I am a function.')

# Assign the function to a variable without parenthesis.
# We don't want to execute the function.
description = my_function

# Accessing the function from the variable I assigned it to.
print(description())
# Output
# I am a function.

إمكانية تعريف دالة داخل دالة أخرى

يمكنك إنشاء دالة داخل دالة أخرى، وهذا سلوك أساسي تعتمد عليه الديكوريترز.

def outer_function():
    def inner_function():
        print('I came from the inner function.')

    # Executing the inner function inside the outer function.
    inner_function()

outer_function()
# Output
# I came from the inner function.

انتبه إلى أن الدالة inner_function لا تكون متاحة خارج الدالة outer_function. وإذا حاولت استدعاءها مباشرة من الخارج فستحصل على الخطأ NameError.

inner_function()
Traceback (most recent call last):
  File "/tmp/my_script.py", line 9, in <module>
    inner_function()
NameError: name 'inner_function' is not defined

إرجاع دالة من داخل دالة أخرى

بما أن الدوال كائنات، فمن الممكن أيضاً إرجاع دالة من داخل دالة أخرى.

def outer_function():
    '''Assign task to student'''
    task = 'Read Python book chapter 3.'

    def inner_function():
        print(task)

    return inner_function

homework = outer_function()
homework()
# Output
# Read Python book chapter 3.

تمرير دالة كوسيط إلى دالة أخرى

من الأسس المهمة كذلك إمكانية تمرير دالة إلى دالة أخرى كوسيط argument، وهذا هو قلب فكرة الديكوريتر.

def friendly_reminder(func):
    '''Reminder for husband'''
    func()
    print("Don't forget to bring your wallet!")

def action():
    print('I am going to the store buy you something nice.')

# Calling the friendly_reminder function with the action function used as an argument.
friendly_reminder(action)

# Output
# I am going to the store buy you something nice.
# Don't forget to bring your wallet!

كيفية إنشاء ديكوريتر في Python

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

def my_decorator_func(func):
    def wrapper_func():
        # Do something before the function.
        func()
        # Do something after the function.

    return wrapper_func

بعد ذلك يمكنك تطبيق الديكوريتر على أي دالة باستخدام الرمز @ أعلى تعريف الدالة مباشرة.

@my_decorator_func
def my_func():
    pass

هذا الأسلوب يجعل الشيفرة أكثر أناقة، لأنك تفصل بين المنطق الأساسي للدالة والمنطق الإضافي الذي تريد تكراره.

مثال عملي: تسجيل تاريخ ووقت تنفيذ الدالة

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

from datetime import datetime

def log_datetime(func):
    '''Log the date and time of a function'''
    def wrapper():
        print( f'Function: {func.__name__}\nRun on: {datetime.today().strftime("%Y-%m-%d %H:%M:%S")}' )
        print(f'{"-" * 30}')
        func()
    return wrapper

@log_datetime
def daily_backup():
    print('Daily backup job has finished.')

daily_backup()

هذا النوع من الديكوريترز شائع جداً في التطبيقات الفعلية، خصوصاً في مهام الصيانة والنسخ الاحتياطي والتتبع التشغيلي.

كيفية تمرير معاملات إلى الديكوريتر

في كثير من الحالات، لن تكون الدالة التي تريد تزيينها خالية من المعاملات. لذلك يجب أن تدعم الدالة الداخلية wrapper استقبال عدد غير محدود من المعاملات باستخدام *args و**kwargs.

  • *args: لاستقبال عدد غير محدود من المعاملات الموضعية.
  • **kwargs: لاستقبال عدد غير محدود من المعاملات المسماة.
def my_decorator_func(func):
    def wrapper_func(*args, **kwargs):
        # Do something before the function.
        func(*args, **kwargs)
        # Do something after the function.
    return wrapper_func

@my_decorator_func
def my_func(my_arg):
    '''Example docstring for function'''
    pass

استخدام *args و**kwargs يمنح الديكوريتر مرونة كبيرة، لأنه يصبح قابلاً للعمل مع طيف واسع من الدوال دون الحاجة إلى إعادة كتابته لكل حالة.

مشكلة فقدان اسم الدالة ووصفها وكيفية حلها

من المشكلات الشائعة عند استخدام الديكوريترز أن الدالة المزينة قد تفقد بعض خصائصها الوصفية مثل __name__ و__doc__، لأن الدالة الأصلية تُستبدل عملياً بالدالة wrapper_func.

print(my_func.__name__)
print(my_func.__doc__)

# Output
# wrapper_func
# None

لحل هذه المشكلة، نستخدم الأداة wraps من المكتبة functools. فهي تنقل خصائص الدالة الأصلية إلى الدالة المغلفة.

from functools import wraps

def my_decorator_func(func):
    @wraps(func)
    def wrapper_func(*args, **kwargs):
        func(*args, **kwargs)
    return wrapper_func

@my_decorator_func
def my_func(my_args):
    '''Example docstring for function'''
    pass

بعد ذلك ستعود البيانات الوصفية إلى الشكل المتوقع:

print(my_func.__name__)
print(my_func.__doc__)

# Output
# my_func
# Example docstring for function

ويُعد استخدام @wraps(func) من أفضل الممارسات التي ينبغي اعتمادها دائماً تقريباً عند بناء ديكوريتر احترافي.

مثال واقعي: قياس الأداء واستهلاك الذاكرة

من أكثر الاستخدامات العملية للديكوريترز مراقبة أداء الدوال. في المثال التالي، يتم قياس زمن التنفيذ واستهلاك الذاكرة باستخدام tracemalloc وperf_counter.

from functools import wraps
import tracemalloc
from time import perf_counter

def measure_performance(func):
    '''Measure performance of a function'''
    @wraps(func)
    def wrapper(*args, **kwargs):
        tracemalloc.start()
        start_time = perf_counter()
        func(*args, **kwargs)
        current, peak = tracemalloc.get_traced_memory()
        finish_time = perf_counter()

        print(f'Function: {func.__name__}')
        print(f'Method: {func.__doc__}')
        print( f'Memory usage:\t\t {current / 10 ** 6 :.6f} MB\n'
               f'Peak memory usage:\t {peak / 10 ** 6 :.6f} MB' )
        print(f'Time elapsed in seconds: {finish_time - start_time:.6f}')
        print(f'{"-" * 40}')
        tracemalloc.stop()
    return wrapper

@measure_performance
def make_list1():
    '''Range'''
    my_list = list(range(100000))

@measure_performance
def make_list2():
    '''List comprehension'''
    my_list = [l for l in range(100000)]

@measure_performance
def make_list3():
    '''Append'''
    my_list = []
    for item in range(100000):
        my_list.append(item)

@measure_performance
def make_list4():
    '''Concatenation'''
    my_list = []
    for item in range(100000):
        my_list = my_list + [item]

print(make_list1())
print(make_list2())
print(make_list3())
print(make_list4())

هذا المثال يوضح بشكل عملي كيف يمكن لديكوريتر واحد أن يضيف طبقة تحليل متقدمة فوق عدة دوال مختلفة، من دون تكرار أي منطق خاص بالقياس داخل كل دالة.

ماذا نستفيد من هذا المثال؟

  • مقارنة أكثر من طريقة لتنفيذ المهمة نفسها.
  • تحليل استهلاك الذاكرة بدقة.
  • قياس الزمن الفعلي للتنفيذ.
  • بناء أدوات داخلية تساعد في تحسين الأداء أثناء التطوير.

في المشاريع البرمجية الحقيقية، هذا النوع من الديكوريترز مفيد جداً عند اختبار الخوارزميات أو تقييم كفاءة أجزاء حساسة من التطبيق.

ديكوريترز الأصناف Class Decorators في Python

لا يقتصر استخدام الديكوريترز على الدوال فقط، بل يمكن تطبيق الفكرة أيضاً باستخدام الأصناف classes. في هذه الحالة، يمكن تعريف السلوك داخل الصنف عبر الدالة الخاصة __call__.

المثال التالي ينفذ آلية تحد من عدد مرات استدعاء دالة تستعلم عن بيانات من واجهة برمجة تطبيقات API. وعندما يصل العدد إلى الحد المسموح، يمنع الديكوريتر تشغيل الدالة مجدداً.

import requests

class LimitQuery:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.limit = args[0]
        if self.count < self.limit:
            self.count += 1
            return self.func(*args, **kwargs)
        else:
            print(f'No queries left. All {self.count} queries used.')
            return

@LimitQuery
def get_coin_price(limit):
    '''View the Bitcoin Price Index (BPI)'''
    url = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json')
    if url.status_code == 200:
        text = url.json()
        return f"$ {float(text['bpi']['USD']['rate_float']):.2f}"

print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))

الفائدة الكبيرة هنا أن الصنف يحتفظ بالحالة state بين الاستدعاءات، مثل عدد مرات التنفيذ. وهذا يجعل ديكوريتر الصنف مناسباً للحالات التي تتطلب تتبعاً مستمراً أو إدارة موارد محددة.

أفضل الممارسات عند استخدام ديكوريترز Python

  • استخدم @wraps دائماً للحفاظ على اسم الدالة ووصفها وخصائصها.
  • اجعل الديكوريتر يؤدي مهمة واحدة واضحة لتسهيل صيانته.
  • تجنب الإفراط في التزيين إذا كان سيجعل تدفق التنفيذ صعب الفهم.
  • اختبر الديكوريتر مع دوال تستقبل معاملات مختلفة.
  • استخدم الديكوريتر عندما يقلل التكرار فعلاً، لا لمجرد الزخرفة البرمجية.

الخلاصة التقنية

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

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *