البرمجة الوظيفية Functional Programming وتطبيقها في بايثون – بايثون

66
0


تمثل البرمجة الوظيفية Functional Programming نموذجًا paradigm يؤكد على كتابة دوال تُجري العمليات الحسابية دون إجراء تعديلات على المتغيرات العامة أو على الحالات خارج الكائنات، مثل الملفات على القرص الصلب أو الاتصالات بالإنترنت أو قواعد البيانات. ويتمحور تصميم بعض لغات البرمجة مثل Erlang و Lisp و Haskell كثيرًا حول مفاهيم البرمجة الوظيفية، أما لغة بايثون ورغم عدم تقيدها بنموذج البرمجة الوظيفية، إلا أنها تحمل بعضًا من ميزاتها، وأهم ما يمكن لبرامج بايثون استخدامه من ميزات البرمجة الوظيفية هي الدوال خالية الآثار الجانبية side-effect-free functions والدوال عالية المستوى higher-order functions والدوال المجهولة lambda functions.

يستعرض هذا المقال نموذج البرمجة الوظيفية ومزايا استخدام الدوال وفقًا لهذا النموذج.

الآثار الجانبية Side Effects

تُعرّف الآثار الجانبية side effects بأنها أي تغييرات تجريها الدالة للأجزاء من البرنامج الواقعة خارج شيفرتها الخاصة ومتغيراتها المحلية، ولتوضيح هذه الفكرة، ننشئ دالة للطرح باسم ()subtract تستخدم عامل الطرح في بايثون (-):

>>> def subtract(number1, number2):
...     return number1 - number2
...
>>> subtract(123, 987)
-864

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

لنطلع الآن على الدالة التالية المسماة ()addToTotal، والتي تضيف الوسطاء العددية خاصتها إلى متغير عام باسم TOTAL على النحو التالي:

>>> TOTAL = 0
>>> def addToTotal(amount):
...     global TOTAL
...     TOTAL += amount
...     return TOTAL
...
>>> addToTotal(10)
10
>>> addToTotal(10)
20
>>> addToTotal(9999)
10019
>>> TOTAL
10019

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

تشمل الآثار الجانبية أيضًا التغييرات المكانية على الكائنات المتغيرة التي تشير إلى ما هو خارج الدالة. على سبيل المثال، تعدّل الدالة ()removeLastCatFromList التالية وسيط القائمة مكانيًا:

>>> def removeLastCatFromList(petSpecies):
...     if len(petSpecies) > 0 and petSpecies[-1] == 'cat':
...         petSpecies.pop()
...
>>> myPets = ['dog', 'cat', 'bird', 'cat']
>>> removeLastCatFromList(myPets)
>>> myPets
['dog', 'cat', 'bird']

يحمل كل من المتغير myPets والمعامل petSpecies في المثال السابق مرجعًا إلى نفس القائمة. أي تعديلات مكانية تجري على كائن القائمة ضمن الدالة ستجري أيضًا خارجها، ما يجعل هذا التعديل أثرًا جانبيًا.

تُعد الدوال الحتمية deterministic functions أحد المفاهيم ذات الصلة هنا، والتي تعرّف بأنها الدوال التي تعيد دومًا القيمة نفسها من أجل نفس الوسطاء، فمثلًا سيعيد الاستدعاء (subtract(123, 987 دومًا القيمة “864-“، وكذلك تعيد دالة بايثون المبنية مسبقًا ()round (والتي تعمل على تقريب العدد إلى أقرب عدد صحيح) الرقم 3 لدى تمرير العدد 3.14 وسيطًا إليها. لا تعيد الدوال غير الحتمية بالضرورة نفس القيمة من أجل نفس الوسطاء، فعلى سبيل المثال، يعيد الاستدعاء (random.randint(1, 10 قيمةً عشوائيةً محصورةً بين 1 و10، والدالة ()time.time رغم عدم احتوائها على وسطاء إلا أنها تعيد قيمة مختلفة تبعًا للتوقيت الذي تشير إليه ساعة الحاسب لحظة استدعائها؛ ففي حالة الدالة ()time.time، تعد الساعة مصدرًا خارجيًا يمثّل دخلًا للدالة كما يفعل الوسيط. لا تُعد جميع الدوال المعتمدة على مصادر من خارجها، سواء كانت متغيرات عامة، أو ملفات على القرص الصلب، أو قواعد بيانات، أو اتصالات بالإنترنت دوالًا حتمية.

إحدى فوائد الدوال الحتمية هي إمكانية التخزين المؤقت لقيمها، فما من ضرورة مثلًا لاستدعاء الدالة ()subtract أكثر من مرة لحساب الفرق بين نفس العددين 123 و987 طالما أنها قادرة على تذكّر القيمة المعادة من المرة الأولى لاستدعائها مع تمرير نفس هذين الوسيطين، وبالتالي تتيح لنا الدوال الحتمية إمكانية المفاضلة ما بين الزمن والمساحة التخزينية، بمعنى زيادة سرعة تنفيذ دالة على حساب المساحة التخزينية اللازمة في الذاكرة لتخزين النتائج السابقة لهذه الدالة.

وتدعى الدالة الحتمية خالية الآثار الجانبية بالدالة النقية pure function. تسعى البرمجة الوظيفية لإنشاء دوال نقية فقط في برامجها. إذ توفّر الدوال النقية العديد من المزايا، ومنها:

  • مناسبة تمامًا لاختبار وحدة مستقلةً، إذ أنها لا تتطلب إعداد أي مصادر خارجية.
  • يسهل في الدوال النقية إعادة الحصول على الخطأ نفسه باستدعائها من أجل نفس الوسطاء، لفهم أسباب الخطأ وإصلاحه.
  • الدوال النقية قادرة على استدعاء دوال نقية أخرى مع بقائها نقية.
  • تكون الدوال النقية في البرامج متعددة المستخدمين المتزامنين multithreaded programs آمنة من الخيوط thread-safe، إذ يمكن تشغيلها في وقتٍ واحد بأمان. موضوع البرامج متعددة المستخدمين المتزامنين multithreaded programs خارج اهتمامات مقالنا هذا.
  • يمكن إجراء استدعاءات متعددة متزامنة وبأي ترتيب للدوال النقية من قبل نوى وحدة المعالجة المركزية CPU المتوازية، أو برنامج متعدد مستخدمين كونها لا تعتمد على أي مصدر خارجي يفرض تشغيلها بترتيب معين.

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

الدوال عالية المستوى Higher-Order Functions

يمكن للدوال عالية المستوى Higher-Order Functions استقبال الدوال الأخرى مثل وسطاء لها، أو أن تعيد دوال أخرى مثل قيمة معادة، فعلى سبيل المثال، لنعرّف دالة باسم ()callItTwice تعمل على استدعاء أي دالة أخرى مرتين:

>>> def callItTwice(func, *args, **kwargs):
...     func(*args, **kwargs)
...     func(*args, **kwargs)
...
>>> callItTwice(print, 'Hello, world!')
Hello, world!
Hello, world!

تعمل الدالة ()callItTwice السابقة مع أي دالة تمرر إليها، إذ تعد الدوال في بايثون كائنات من الدرجة الأولى، بمعنى أنها كغيرها من الكائنات من الممكن تخزينها ضمن متغيرات أو تمريرها مثل وسطاء أو استخدامها مثل قيم معادة.

الدوال المجهولة anonymous functions

الدوال لامدا lambda functions أو الدوال المجهولة anonymous functions أو الدوال عديمة الاسم nameless functions هي دوال بسيطة عديمة الأسماء وتتألف شيفرتها من تعليمة return فقط، وتُستخدم عادةً الدوال المجهولة لدى تمرير الدوال مثل وسطاء لدول أخرى، فعلى سبيل المثال، من الممكن إنشاء دالة عادية تستقبل قائمة متضمنة لطول وعرض مستطيل بأبعاد 10 و4 على النحو التالي:

>>> def rectanglePerimeter(rect):
...     return (rect[0] * 2) + (rect[1] * 2)
...
>>> myRectangle = [4, 10]
>>> rectanglePerimeter(myRectangle)
28

فتبدو الدالة المجهولة المكافئة كما يلي:

lambda rect: (rect[0] * 2) + (rect[1] * 2)

نستخدم الكلمة المفتاحية lambda للتصريح عن دالة مجهولة في بايثون متبوعةً بقائمة بالمعاملات (في حال وجودها) مفصولةً فيما بينها بمحارف فاصلة، ومن ثم نقطتين رأسيتين ونهايةً تعبير برمجي يُمثّل القيمة المعادة. بما أن الدوال هي كائنات من الدرجة الأولى في بايثون، يمكن إسناد الدالة المجهولة إلى متغير ما، على غرار ما تؤديه التعليمة def، على النحو التالي:

>>> rectanglePerimeter = lambda rect: (rect[0] * 2) + (rect[1] * 2)
>>> rectanglePerimeter([4, 10])
28

أسندنا في الشيفرة السابقة الدالة المجهولة إلى متغير اسمه rectanglePerimeter، ما يعطي بالنتيجة دالةً باسم ()rectanglePerimeter، وبذلك نجد أن الدوال المُنشأة باستخدام التعليمة lambda تماثل تلك المُنشأة باستخدام التعليمة def.

ملاحظة: يفضل في الشيفرات الواقعية استخدام التعليمة def على إسناد دالة مجهولة إلى متغير، إذ أن الغرض الأساسي من وجود الدوال المجهولة هو استخدامها في الحالات التي لا تحتاج فيها الدالة إلى اسم.

صياغة الدوال المجهولة مفيدة في تخصيص دوال صغيرة لتعمل مثل وسطاء عند استدعاء دوال أخرى. على سبيل المثال، تمتلك الدالة ()sorted وسيطًا مسمى يدعى key يسمح بتحديد دالة، فبدلًا من فرز العناصر في قائمة ما وفقًا لقيمها، تفرزها وفقًا للقيمة المعادة من تلك الدالة الممررة إلى الوسيط key. مررنا في المثال التالي دالةُ مجهولةً إلى الدالة ()sorted تعيد محيط مستطيل مُعطى الأبعاد، ما يجعل الدالة ()sorted تفرز العناصر اعتمادًا على المحيط المحسوب من طوله وعرضه [width, height] الواردان في القائمة، بدلًا من فرزها اعتمادًا على قيم الطول والعرض نفسها، على النحو التالي:

>>> rects = [[10, 2], [3, 6], [2, 4], [3, 9], [10, 7], [9, 9]]
>>> sorted(rects, key=lambda rect: (rect[0] * 2) + (rect[1] * 2))
[[2, 4], [3, 6], [10, 2], [3, 9], [10, 7], [9, 9]]

بدلًا من فرز القيم [10,2] أو [3,6] مثلًا، أصبحت الدالة تفرزها اعتمادًا على قيمة المحيط المعادة المتمثلة بالأعداد الصحيحة 24 و18 على التوالي. تعد الدوال المجهولة اختصارًا مناسبًا للصياغة، إذ من الممكن تعريف دالة مجهولة صغيرة مؤلفة من سطر برمجي واحد، بدلًا من تعريف دالة مسماة جديدة باستخدام التعليمة def.

الربط والترشيح باستخدام بنى اشتمال القوائم List Comprehensions

كانت الدالتين ()map و ()filter في الإصدارات الأقدم من بايثون من الدوال عالية المستوى الشائعة القادرة على ربط القوائم وترشيحها، الأمر الذي كان يجري غالبًا بمساعدة الدوال المجهولة. تستطيع عملية الربط Mapping إنشاء قائمة قيم اعتمادًا على قيم قائمة أخرى؛ بينما ينشئ الترشيح قائمةً تتضمن فقط القيم التي تحقق معيارًا معينًا من قائمة أخرى.

على سبيل المثال، لو أردنا إنشاء قائمة جديدة تتضمن قيم من نوع السلاسل النصية بدلًا من بدلًا من الأعداد الصحيحة في القائمة التالية [7 ,6 ,1 ,12 ,19 ,18 ,16 ,8]، فمكن الممكن تمرير كل من القائمة هذه والتابع المجهول (lambda n: str(n إلى دالة الربط ()map، على النحو التالي:

>>> mapObj = map(lambda n: str(n), [8, 16, 18, 19, 12, 1, 6, 7])
>>> list(mapObj)
['8', '16', '18', '19', '12', '1', '6', '7']

تعيد الدالة ()map كائنًا من النوع map، والذي يمكن تحويله إلى قائمة بتمريره إلى الدالة ()list، وبذلك تتضمن القائمة المربوطة الآن قيمًا من نوع سلاسل محرفية موافقة للأعداد الصحيحة الموجودة في القائمة الأصلية. تعمل دالة الترشيح ()filter بآلية مشابهة، إلا أن وسيط الدالة المجهولة في هذه الحالة يحدد العناصر من القائمة التي ستبقى (عند إعادة الدالة المجهولة للقيمة True من أجل هذا العنصر) وتلك التي ستُرشّح (عند إعادة الدالة المجهولة للقيمة False من أجل هذا العنصر). على سبيل المثال، يمكن تمرير الدالة المجهولة lambda n: n % 2 == 0 لترشيح أي أعداد فردية في السلسلة على النحو التالي:

>>> filterObj = filter(lambda n: n % 2 == 0, [8, 16, 18, 19, 12, 1, 6, 7])
>>> list(filterObj)
[8, 16, 18, 12, 6]

تعيد الدالة ()filter كائن ترشيح من النوع filter، والذي يمكن أيضًا تمريره إلى الدالة ()list، وبذلك تبقى الأعداد الزوجية فقط في القائمة بعد الترشيح.

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

سنعيد فيما يلي مثال الدالة ()map ولكن باستخدام بنية اشتمال قوائم:

>>> [str(n) for n in [8, 16, 18, 19, 12, 1, 6, 7]]
['8', '16', '18', '19', '12', '1', '6', '7']

نلاحظ أن الجزئية (str(n من بنية اشتمال القوائم في الشيفرة أعلاه تشابه في وظيفتها الدالة المجهولة (lambda n: str(n من الطريقة السابقة.

وفيما يلي نعيد مثال الدالة ()filter ولكن باستخدام بنية اشتمال قوائم:

>>> [n for n in [8, 16, 18, 19, 12, 1, 6, 7] if n % 2 == 0]
[8, 16, 18, 12, 6]

نلاحظ أن الجزئية n % 2 == 0 من بنية اشتمال القوائم في الشيفرة أعلاه تشابه في وظيفتها الدالة المجهولة lambda n: n % 2 == 0 من الطريقة السابقة.

تتعامل العديد من اللغات مع مفهوم الدوال على أنها كائنات من الدرجة الأولى، ما يسمح بوجود دوال عالية المستوى بما يتضمن دوال الربط والترشيح.

ينبغي على القيم المعادة أن تتضمن دوما نمط البيانات نفسه

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

على سبيل المثال، لدينا في الشيفرة التالية دالة تعتمد على رقم عشوائي لتعيد إما عددًا صحيحًا أو سلسلةً نصية:

>>> import random
>>> def returnsTwoTypes():
...     if random.randint(1, 2) == 1:
...         return 42
...     else:
...         return 'forty two'

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

>>> hexNum = hex(returnsTwoTypes())
>>> hexNum
'0x2a'

تعيد دالة بايثون ()hex المبنية مسبقًا سلسلةً نصيةً لقيمة العدد الصحيح الممرر إليها في نظام العد السداسي عشري. ستعمل الشيفرة السابقة على نحو سليم طالما أن الدالة ()returnsTwoTypes تعيد قيمةً عدديةً صحيحة، ما يوحي بأن الشيفرة خالية الأخطاء، إلا أنه بمجرد إعادة الدالة ()returnsTwoTypes لسلسلة نصية، سيظهر الاستثناء التالي:

>>> hexNum = hex(returnsTwoTypes())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object cannot be interpreted as an integer

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

توجد حالة عملية ينبغي الانتباه إليها وهي ألا نجعل الدالة تعيد القيمة None إلا إذا كانت لا تعيد سواها، إذ أن القيمة None هي الوحيدة ضمن نمط البيانات None، فقد نرغب بجعل الدالة تعيد None للدلالة على حدوث خطأ ما (الأمر الذي سنناقشه في الفقرة التالية “إظهار الاستثناءات مقابل إعادة رموز الأخطاء”)، ولكن عليك حصر استخدام None فقط مثل قيمة معادة للدوال التي لا تمتلك أي قيمة معادة ذات معنى، والسبب في ذلك هو أن إعادة القيمة None للدلالة على وقوع خطأ يعد مصدرًا شائعًا للاستثناء غير المعلوم الخاص بنمط البيانات None والدال على استخدام سمة ليست من سمات الكائن 'NoneType' object has no attribute، كما في المثال:

>>> import random
>>> def sometimesReturnsNone():
...     if random.randint(1, 2) == 1:
...         return 'Hello!'
...     else:
...         return None
...
>>> returnVal = sometimesReturnsNone()
>>> returnVal.upper()
'HELLO!'
>>> returnVal = sometimesReturnsNone()
>>> returnVal.upper()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'upper'

رسالة الخطأ السابقة غامضة نسبيًا، وقد تتطلب بعض الجهد لتتبع سببها عودةً إلى دالة تعيد في الأحوال الطبيعية قيمة متوقعة ولكنها قد تعيد القيمة None في حال وقوع خطأ، وقد وقع الخطأ في مثالنا السابق لأن الدالة ()sometimesReturnsNone أعادت القيمة None والتي أسندناها إلى المتغير returnVal. في حين أن رسالة الخطأ توحي بأن الخطأ حصل في استدعاء التابع ()upper.

إظهار الاستثناءات Exceptions مقابل إعادة رموز الأخطاء Error Codes

للمصطلحين استثناء exception وخطأ error نفس المعنى تقريبًا في بايثون وهو: ظرف أو حالة استثنائية في البرنامج تشير عادةً إلى وجود مشكلة. أصبحت الاستثناءات شائعة وبمثابة ميزة للغات البرمجة في الثمانينات والتسعينات وذلك في لغتي C++‎ وجافا، إذ حلت الاستثناءات محل استخدام رموز الأخطاء والتي تمثّل القيم المعادة من الدالة لتشير إلى وجود مشكلة. لعل فائدة الاستثناءات تكمن في كونها تعيد قيمًا متعلقة بعمل الدالة نفسه بدلًا من الإشارة إلى وجود خطأ فحسب.

قد تسبب رموز الأخطاء بحد ذاتها مشاكلًا في البرنامج، فعلى سبيل المثال، تعيد الدالة ()find الخاصة بالتعامل مع السلاسل النصية في بايثون عادةً الفهرس الموافق لمكان وجود سلسلة نصية فرعية ضمن سلسلة نصية رئيسية ما، وفي حال عدم عثورها على السلسلة الفرعية المطلوبة، فإنها تعيد القيمة “1-” مثل رمز خطأ، ولكن وبما أننا قد نستخدم القيمة “1-” للدلالة على رقم فهرس محرف ابتداءً من نهاية السلسلة النصية، فقد يؤدي استخدام “1-” رمزًا لخطأ إلى وقوع خطأ غير مقصود. لنكتب ما يلي ضمن الصدفة التفاعلية كمثال:

>>> print('Letters after b in "Albert":', 'Albert'['Albert'.find('b') + 1:])
Letters after b in "Albert": ert
>>> print('Letters after x in "Albert":', 'Albert'['Albert'.find('x') + 1:])
Letters after x in "Albert": Albert

تقيّم الشيفرة السابقة الجزئية ('Albert'.find('x' إلى رمز الخطأ “1-” (نظرًا لعدم وجود المحرف x ضمن السلسة النصية Albert)، وهذا ما يجعل بدوره التعبير البرمجي [:Albert'['Albert'.find('x') + 1' يُقيّم إلى [:Albert'[-1 + 1' والذي يُقيّم هو الآخر إلى [:Albert'[0' وبالتالي إلى القيمة 'Albert'. لا تمثّل هذه النتيجة تلك المقصودة من الشيفرة، إذ سيظهر استدعاء التابع ()index بدلًا من ()find كما في [:Albert'['Albert'.index('x') + 1' استثناءً، ما يجعل المشكلة جليةً وواضحة.

يعيد التابع ()index الخاص بالسلاسل النصية من جهة أخرى استثناء خطأ القيمة ValueError في حال عدم العثور على الدالة الفرعية، وإذا لم نتعامل مع هذا الاستثناء، سيتوقف البرنامج عن العمل، الأمر الأفضل من عدم ملاحظة وجود خطأ أصلًا.

تنتهي عادةً أسماء أصناف الاستثناءات بالكلمة "Error" وذلك عندما يشير الاستثناء إلى خطأ فعلي، مثل ValueError أو NameError للدلالة على خطأ في الاسم أو SyntaxError للدلالة على خطأ صياغي، أما أصناف الاستثناءات التي تشير إلى حالات استثنائية والتي لا تمثل أخطاء بالضرورة فتتضمن StopIteration أو KeyboardInterrupt أو SystemExit.

الخلاصة

رغم كون بايثون ليست بلغة برمجة وظيفية، إلا أنها تمتلك العديد من الميزات التي تستخدمها هذه اللغات، إذ أن الدوال في بايثون هي كائنات من الدرجة الأولى، ما يعني إمكانية تخزينها ضمن متغيرات وتمريرها مثل وسطاء لدوال أخرى (إذ تدعى تلك الأخيرة بالدوال عالية المستوى)، كما توفر الدوال المجهولة اختصارًا في الصياغة لحالات الحاجة لاستخدام دوال عديمة الاسم ومجهولة مثل وسطاء لدوال عالية المستوى. لعل دالة الربط ()map ودالة الترشيح ()filter من أشهر الدوال عالية المستوى في بايثون، رغم إمكانية تنفيذ نفس وظيفتهما على نحوٍ أسرع بالاعتماد على بنى اشتمال القوائم.

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

ترجمة -وبتصرف- للجزء الثاني من الفصل العاشر “كتابة دوال فعالة في بايثون” من كتاب Beyond the Basic Stuff with Python لصاحبه Al Sweigart.

اقرأ أيضًا



المصدر

اترك تعليقاً

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