المسارات
/
Python
Python
/
المنهج
/
المُزخرفات
ال

المُزخرفات في Python

{zero: "لا توجد تمارين", one: "1 تمرين", two: "تمرينان", few: "%{count} تمارين", many: "%{count} تمرينًا", other: "%{count} تمرين"}

نبذة عن المُزخرفات

المُزخرفات دوال تأخذ دالة أخرى كوسيط بغرض توسيع سلوك الدالة المُمرَّرة أو استبداله. إذا كانت الدالة A مُزخرفًا، وكانت الدالة B وسيطه، فإن الدالة A تُعدِّل سلوك الدالة B أو توسِّعه أو تستبدله دون تعديل كود الدالة B. نقول إن المُزخرف A يُغلِّف الدالة B. وعلى الرغم من أننا نتحدث عن "تعديل" السلوك، فإن الدالة المُغلَّفة لا تتغير فعليًا. فإما أن يُضاف سلوك حول الدالة المُغلَّفة (وحول ما تُرجعه)، وإما أن يُستبدل سلوك الدالة المُغلَّفة بسلوك آخر.

المُزخرف دالة عالية الرتبة

الدالة عالية الرتبة هي دالة تقبل دالة أو أكثر كوسائط، و/أو تُرجع دالة أو أكثر. والدالة التي تُستخدم كوسيط أو تُرجعها دالة أخرى هي دالة من الدرجة الأولى. ودالة Python، باعتبارها كائنًا قابلًا للاستدعاء، هي دالة من الدرجة الأولى يمكن تخزينها في متغير أو استخدامها كوسيط، تمامًا مثل أي قيمة أو كائن آخر. وتتعاون الدوال عالية الرتبة مع الدوال من الدرجة الأولى لجعل المُزخرفات ممكنة.

كيف يبدو استخدام المُزخرف

توضع علامة @ قبل اسم المُزخرف، مباشرةً فوق الدالة المراد تزيينها، هكذا:

@decorator
def decorated_function():
    pass

وبعض المُزخرفات تقبل وسائط:

@decorator_with_arg(name="Bob")
def decorated_function2():
    pass

إذا كان للمُزخرف وسائط افتراضية معرَّفة، فيجب استخدام الأقواس الهلالية في استدعاء @decorator() ليعمل المُزخرف، تمامًا كما تفعل عند استدعاء أي دالة:

@decorator_with_default_arg()
def decorated_function3():
    pass

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

TypeError: decorator_with_pos_arg() missing 1 required positional argument: 'name'

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

def function():
    pass

function = decorator(function)


def function2():
    pass

function2 = decorator_with_arg(name="Bob")(function2)


def function3():
    pass

function3 = decorator_with_default_arg()(function3)

كتابة مُزخرف بسيط

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

المُزخرفات دوال تأخذ وسيطًا واحدًا على الأقل، هو الدالة التي تُغلِّفها. وهي عادةً ما تُرجع إما الدالة المُغلَّفة، وإما نتيجة تعبير يستخدم الدالة المُغلَّفة.

يمكن كتابة مُزخرف بسيط، وهو الذي يُرجع الدالة المُغلَّفة فحسب، على النحو التالي:

>>> def do_nothing(func):
...     return func
...
... @do_nothing
... def function4():
...     return 4
...
>>> print(function4())
4

وقد يقتصر المُزخرف على إضافة آثار جانبية، مثل معلومات إضافية تُستخدم في التسجيل:

>>> def my_logger(func):
...     print(f"Entering {func.__name__}")
...     return func
...
... @my_logger
... def my_func():
...     print("Hello")
...
>>> my_func()
Entering my_func
Hello

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

الدوال الداخلية

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

مُزخرف للتحقق يستخدم دالة داخلية

فيما يلي مثال على مُزخرف يُستخدم للتحقق:

def my_validator(func):
    def my_wrapper(world):
        print(f"Entering {func.__name__} with {world} argument")
        
        if world == "Pluto":
            print("Pluto is not a planet!")
        else:
            return func(world)
    return my_wrapper

@my_validator
def my_func(planet):
    print(f"Hello, {planet}!")

my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!

my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Pluto is not a planet!

في السطر الأول لدينا تعريف وسيط المُزخرف func. وفي السطر التالي تعريف الدالة الداخلية للمُزخرف، وهي التي تُغلِّف الوسيط func. ولأن الدالة الداخلية تُغلِّف الوسيط func الخاص بالمُزخرف، فإنها تستقبل الوسيط نفسه الذي يُمرَّر إلى func. لاحظ أن الغلاف ليس مضطرًا لاستخدام اسم الوسيط نفسه المُعرَّف في func. فالدالة الأصلية تستخدم planet والمُزخرف يستخدم world، ومع ذلك يعمل المُزخرف كما هو.

تُرجع الدالة الداخلية إما func، وإما، إذا كان world == "Pluto"، تطبع أن بلوتو ليس كوكبًا. ويمكن برمجتها لترفع ValueError بدلًا من ذلك. إذن، تُغلِّف الدالة الداخلية الوسيط func، وتُرجع إما func، وإما تفعل شيئًا يحل محل ما كان func سيفعله. ويُرجع المُزخرف دالته الداخلية. وقد تُرجع الدالة الداخلية الدالة الأصلية المُمرَّرة أو لا تُرجعها. وتبعًا للكود الذي يُنفَّذ شرطيًا في دالة الغلاف أو الدالة الداخلية، فقد يُرجع func، أو قد يُرفع خطأ، أو قد تُرجع قيمة من نوع القيمة التي يُرجعها func.

تزيين دالة تستقبل أي عدد من الوسائط

يمكن كتابة مُزخرفات لدوال تستقبل أي عدد من الوسائط باستخدام الصيغة *args و**kwargs.

فيما يلي مثال على مُزخرف لدالة تستقبل أي عدد من الوسائط:

def double(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) * 2
    return wrapper

@double
def add(*args):
    return sum(args)

print(add(2, 3, 4))
#-> 18

print(add(2, 3, 4, 5, 6))
#-> 40

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

المُزخرفات التي لها معاملات خاصة بها

فيما يلي مثال على مُزخرف يمكن ضبطه لضرب القيمة التي تُرجعها الدالة المُزخرفة في أي مقدار تختاره:

def multi(factor=1):
     if factor == 0:
        raise ValueError("factor must not be 0")

     def outer_wrapper(func):
         def inner_wrapper(*args, **kwargs):
             return func(*args, **kwargs) * factor
         return inner_wrapper
     return outer_wrapper

@multi(factor=3)
def add(*args):
    return sum(args)

print(add(2, 3, 4))
#-> 27

print(add(2, 3, 4, 5, 6))
#-> 60

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

فيما يلي مثال على مُزخرف ذي معاملات يتحكم في ما إذا كان يتحقق من الوسيط المُمرَّر إلى الدالة الأصلية:

def check_for_pluto(check=True):
    def my_validator(func):
        def my_wrapper(world):
            print(f"Entering {func.__name__} with {world} argument")
            if check and world == "Pluto":
                print("Pluto is not a planet!")
            else:
                return func(world)
        
        return my_wrapper
    return my_validator

@check_for_pluto(check=False)
def my_func(planet):
    print(f"Hello, {planet}!")

my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!

my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Hello, Pluto!

وهذا يتيح التبديل بسهولة بين التحقق من Pluto أو عدمه، من دون الحاجة إلى تعديل my_func.

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة