الدالة functools.singledispatch()‎ في بايثون

من موسوعة حسوب
مراجعة 18:12، 24 يونيو 2018 بواسطة عبد-الهادي-الديوري (نقاش | مساهمات)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

تُحوّل الدّالة functools.singledispatch()‎ دالّةً عاديّة إلى دالّة عموميّة وحيدة الإرسال (single-dispatch generic function).

الدّالة العموميّة هي كلّ دالّة تتكوّن من عدّة دوال تُنفّذ نفس العمليّة لعدّة أنواع. تُحدّد الدّالة التي ستُنفّذ عبر خوارزميّة الإرسال (dispatch algorithm).

تكون الدّالة العموميّة وحيدةَ إرسالٍ إذا كان نوع مُعامل واحد هو الذي يُحدّد الدّالة التي ستُنفَّذ.

البنية العامة

@functools.singledispatch

المعاملات

لا توجد مُعاملات

القيمة المعادة

دالّة عموميّة وحيدة الإرسال.

أمثلة

لإنشاء دالّة عموميّة، زخرِفها بالمُزخرِف ‎@singledispatch‎. لاحظ أنّ الإرسال يحدث عند نوع أوّل مُعامل:

from functools import singledispatch
@singledispatch
def fun(arg, verbose=False): # المُعامل الثّاني يُحدّد ما إذا كانت الطّباعة ستطبع القيمةَ مع معلومات إضافيّة  أو لا
    if verbose:
        print("لنطبع القيمة ", end=" ")
    print(arg)

لإنشاء دوال تتصرّف حسب نوع المُعامل، استعمل الخاصيّة register()‎ التّابعة للدّالة العموميّة. وهي مُزخرفٌ يأخذ نوع مُعاملٍ ويُزخرف دالّة لاستعمالها كمُعالج للمُعاملات من النّوع المُعطى. على سبيل المثال، سننشئ دالّتين، إحداهما ستُنفّذ عندما يكون نوع المُعامل الأوّل عددًا صحيحًا، والأخرى تُنفّذ عندما يكون المُعامل الأوّل قائمةً:

@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print("أليست الأعداد جميلة؟", end=" ")
    print(arg)

@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print("لنُرقّم القائمة")
    for i, elem in enumerate(arg):
        print(i, elem)

لتسجيل تعابير lambda والدّوال المُعرّفة مُسبقًا، يُمكن استخدام الخاصيّة register()‎ على أنّها دالّة:

def nothing(arg, verbose=False):
    print("العَدَمْ")

fun.register(type(None), nothing)

تُعيد الخاصيّة register()‎ الدّالة غير المُزخرَفة، ما يُمكّننا من تكديس المُزخرفات بعضها فوق بعض، وإجراء عمليّات تسلسل الكائنات (object serialization)، إضافةً إلى إنشاء اختبارات وحدة لكلّ دالّة على حدة:

from decimal import Decimal

@fun.register(float)
@fun.register(Decimal)
def fun_num(arg, verbose=False):
    if verbose:
        print("نصفُ العدد هو ", end=" ")
    print(arg / 2)

# >>> fun_num is fun
# False

عندما تُستدعى الدّالة، ستتصرّف حسب نوع أوّل مُعامل كما يلي:

>>> fun("Hello, world.")
Hello, world.
>>> fun("اختبار", verbose=True)
لنطبع القيمة  اختبار
>>> fun(42, verbose=True)
أليست الأعداد جميلة؟ 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
لنُرقّم القائمة
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
العَدَمْ
>>> fun(1.23, verbose=True)
نصفُ العدد هو  0.615

المثال كاملًا:

from decimal import Decimal
from functools import singledispatch


@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("لنطبع القيمة ", end=" ")
    print(arg)


@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print("أليست الأعداد جميلة؟", end=" ")
    print(arg)


@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print("لنُرقّم القائمة")
    for i, elem in enumerate(arg):
        print(i, elem)


def nothing(arg, verbose=False):
    print("العَدَمْ")


fun.register(type(None), nothing)


@fun.register(float)
@fun.register(Decimal)
def fun_num(arg, verbose=False):
    if verbose:
        print("نصفُ العدد هو ", end=" ")
    print(arg / 2)

# >>> fun_num is fun
# False

عندما لا تكون هناك أيّة دالّة مُسجّلة لنوع مُعيّن (مثل المُعاملات من نوع السّلاسل النّصيّة مثلًا في المثال أعلاه)، فسيُستعمل ترتيب البحث عن التوابع (method resolution search order) للحصول على دالّة أكثر عموميّة لمُعالجة النّوع (فمثلًا، إن لم توجد دالّة لمُعالجة النّوع Child، فسيُبحث عن دالّة تُعالج النّوع الأب Parent وستُطبّق على الابن الذي يرثُ من أبيه). تُسجَّل الدّالة الأصليّة التي تُزخرف بالمُزخرِف ‎@singledispatch‎ لمُعالجة النّوع الأساس object‎، ما يعني أنّها تُعتَمدُ إن لم توجد دالّة مُعالجة مُناسبة.

للتّحقّق من الدّالة المُعالجة التي ستُنفّذ لنوع مُعيّن، استعمل الخاصيّة dispatch()‎‎:

>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)    # لاحظ أنّ هذه هي الدّالة الأصليّة والافتراضيّة لأنّنا لم نُحدّد دالّة لتُعالج القواميس
<function fun at 0x103fe0000>

للوصول إلى جميع الدّوال المُعالجة، استعمل الخاصيّة registry‎‎ القابلة للقراءة فقط (read-only):

>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
          <class 'decimal.Decimal'>, <class 'list'>,
          <class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>

انظر أيضًا

مصادر