اذهب إلى المحتوى

البرمجة باستخدام الوحدات


أسامة دمراني

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

وتعتمد فكرة البرمجة باستخدام الوحدات على السماح للمبرمج بتوسيع الإمكانيات الموجودة في لغة البرمجة، عبر تجميع أجزاء من البرنامج في وحدات يمكن توصيلها ببرامجنا، وقد كانت الصورة الأولى للوحدات هي البرامج الفرعية subroutines التي تمثل كتلةً من الشيفرة نستطيع القفز إليها، بدلًا من استخدام GOTO التي ذكرناها في المقال السابق، لكنها تقفز عند تمام الكتلة عائدةً إلى المكان الذي استدعيت منه، ويُعرف هذا الأسلوب من الوحدات بالإجراء procedure أو الدالة function، بل إن مفهوم الوحدة module نفسه يتغير في بعض اللغات مثل بايثون، ليأخذ معنىً أكثر دقة.

استخدام الدوال

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

للدالة الهيكل الأساسي التالي:

aValue = someFunction ( anArgument, another, etc... )

يأخذ المتغير aValue القيمة التي نحصل عليها باستدعاء الدالة someFunction، والتي تقبل عدة وسطاء arguments بين القوسين -وقد لا يوجد أي وسيط-، وتعاملها على أنها متغيرات داخلية، وتستطيع الدوال أن تستدعي دوالًا أخرى داخلها.

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

لننظر الآن في بعض الأمثلة في لغاتنا الثلاث، ولنرى الدوال عمليًا:

الدالة Mid في VBScript

تعيد الدالة Mid(aString, start, length)‎ مجموعة المحارف من aString بدءًا من start وبعدد محارف length، حيث ستعرض الشيفرة التالية "Good EVENING" مثلًا.

<script type="text/vbscript">
Dim time
time = "MORNING EVENING AFTERNOON"
MsgBox "Good" & Mid(time, 8, 8)
</script>

نلاحظ أن VBScript لا تتطلب أقواسًا لجمع وسطاء الدالة بل تكتفي بالمسافات كما كنا نفعل مع MsgBox، لكن عند جمعنا دالتين -كما فعلنا هنا- فيجب أن تستخدم الدالة الداخلية أقواسًا، والنصيحة العامة هي استخدام الأقواس عندما نشك هل يجب استخدامها أم لا.

الدالة Date في VBScript

تعيد الشيفرة التالية التاريخ الحالي للنظام:

<script type="text/vbscript">
MsgBox Date
</script>

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

الدالة startString.replace في جافاسكربت

تعيد دالة startString.replace(searchString, newString)‎ سلسلةً نصيةً جديدةً مع وضع newString بدلًا من searchString‎ في startString.

<script type="text/javascript">
var r,s = "A long and winding road";
document.write("Original = " + s + "<BR>");
r = s.replace("long", "short");
document.write("Result  = " + r);
</script>

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

الدالة Math.pow في جافاسكربت

نستخدم الدالة pow(x,y)‎ في وحدة Math -التي ذكرناها في مقال الخامس: البيانات وأنواعها- لرفع x إلى الأس y:

<script type="text/javascript">
document.write( Math.pow(2,3) );
</script>

الدالة pow في بايثون

تستخدم بايثون دالة pow(x,y)‎ لرفع x إلى الأس y:

>>> x = 2   #  سنستخدم 2 كرقم قاعدة
>>> for y in range(0,11):
...    print( pow(x,y) )    # y ارفع 2 إلى الأس
                            # أي من 0-10

نولد هنا قيم y من 0 إلى 10، ونستدعي الدالة المضمنة pow()‎ لتمرير وسيطين هما x وy، ونستبدل القيم الحالية لكل منهما في استدعاء pow()‎ في كل مرة، ثم نطبع النتيجة.

اقتباس

تجدر الإشارة إلى أن العامل الأسي ** في بايثون يكافئ الدالة pow()‎.

الدالة dir في بايثون

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

>>> print( dir(__builtins__) )

لاحظ أن builtins هي إحدى الكلمات السحرية في بايثون، وعلى ذلك يجب إحاطتها بزوج من شرطتين سفليتين على كل جانب. وإذا أردنا استخدام dir()‎ على أي وحدة، فسنحتاج إلى استيرادها أولًا باستخدام import، وإلا فستخبرنا بايثون أنها لا تتعرف عليها.

>>> import sys
>>> dir(sys)

لقد استوردنا وحدة sys في المثال أعلاه، وهي الوحدة التي ذكرناها أول مرة في مقال التسلسلات البسيطة، ونستطيع أن نرى كلًا من exit وargv وstdin وstdout في الخرج الأخير لدالة dir، بين الأشياء الأخرى الموجودة في وحدة sys.

لننظر الآن في وحدات بايثون بقليل من التفصيل قبل أن ننتقل إلى ما بعدها.

استخدام الوحدات في بايثون

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

الوحدة sys

رأينا sys سابقًا عند الخروج من بايثون، وهي تحتوي على مجموعة من الدوال المفيدة كدالة dir التي سبق شرحها، ويجب أن نستورد وحدة sys أولًا لنصل إلى تلك الدوال:

import sys        # نجعل الدوال متاحة
print( sys.path ) # نرى أين تبحث بايثون عن الدوال
sys.exit()        # 'sys' أسبقها بـ

وإذا علمنا أننا سنستخدم الدوال بكثرة وأنها لن تحمل نفس أسماء الدوال التي استوردناها أو أنشأناها؛ فعندئذ نستطيع فعل ما يلي:

from sys import *  # sys استورد جميع الأسماء في  
print( path )      # يمكن استخدامها دون تحديد السابقة 'sys'
exit() 

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

from sys import path, exit  # استورد الدوال التي تحتاجها فقط
exit()                      # استخدم دون تحديد السابقة 'sys'

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

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

import SimpleXMLRPCServer as s
s.SimpleXMLRPCRequestHandler()

لاحظ كيف أخبرنا بايثون أن تعُد s اختصارًا لـ SimpleXMLRPCServer، ولن نحتاج بعد ذلك إلا إلى كتابة s كي نستخدم دوال الوحدة، مما قلل من الكتابة.

وحدات بايثون الأخرى

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

  • وحدة sys: تسمح بالتفاعل مع نظام بايثون:
    • exit()‎: للخروج.
    • argv: للوصول إلى وسطاء سطر الأوامر.
    • path: للوصول إلى مسار البحث الخاص بوحدة النظام.
    • ps1: لتغيير محث بايثون ‎>>>‎.
  • وحدة os: تسمح بالتفاعل مع نظام التشغيل:
    • name: تعطينا اسم نظام التشغيل الحالي، وهي مفيدة في البرامج المحمولة التي لا تحتاج إلى تثبيت.
    • system()‎: تنفذ أمرًا من أوامر نظام التشغيل.
    • mkdir()‎: تنشئ مجلدًا.
    • getcwd()‎: تعطينا مجلد العمل الحالي.
  • وحدة re: تسمح هذه الوحدة بتعديل السلاسل النصية باستخدام التعابير النمطية regular expressions لنظام يونكس:
    • search()‎: تبحث عن النمط في أي موضع في السلسلة النصية.
    • match()‎: تبحث في البداية فقط.
    • findall()‎: تبحث عن جميع مرات الورود في سلسلة نصية.
    • split()‎: تقسيم السلسلة النصية إلى حقول مفصولة بالنمط.
    • ()sub وsubn()‎: استبدال السلاسل النصية.
    • وحدة math: تسمح بالوصول إلى العديد من الدوال الرياضية.
    • ()sin وcos()‎: دوال حساب المثلثات.
    • log(), log10()‎: اللوغاريتمات الطبيعية والعشرية.
    • ()ceil وfloor()‎: دالتا الجزء الصحيح floor والمتمم الصحيح الأعلى ceiling.
    • pi وe: الثوابت الطبيعية.
    • وحدة time: دوال التاريخ والوقت.
    • time()‎: تحصل على الوقت الحالي بالثواني.
    • gmtime()‎: تحول الوقت بالثواني إلى التوقيت العالمي UTC (أو GMT).
    • localtime()‎: التحويل إلى التوقيت المحلي.
    • mktime()‎: معكوس التوقيت المحلي.
    • strftime()‎: تنسيق السلسلة النصية للوقت، مثل YYYYMMDD أو DDMMYYYY.
    • sleep()‎: إيقاف البرنامج مؤقتًا لمدة n ثانية.
    • وحدة random: تولد أرقامًا عشوائية، وهي مفيدة في برمجة الألعاب.
    • randint()‎: تولد عددًا صحيحًا عشوائيًا بين نقطتين تضمَّنان في التوليد.
    • sample()‎: تولد قائمةً فرعيةً عشوائيًا من قائمة أكبر.
    • seed()‎: إعادة ضبط مفتاح توليد الأعداد.

هذه الوحدات على كثرتها ليست إلا شيئًا يسيرًا من الوحدات التي توفرها بايثون، والتي تزيد على مئة واحدة وأكثر يمكن تحميلها. تذكر أننا نستطيع استخدام dir()‎ وhelp()‎ للحصول على معلومات عن كيفية استخدام الدوال المختلفة، وأن المكتبة القياسية موثقة جيدًا في فهرس وحدات بايثون، ومن المصادر الجيدة للوحدات الإضافية فهرس حزم بايثون، الذي ضم عند كتابتنا لهذا المقال أكثر من مئة ألف حزمة، وكذلك مشروع SciPy الذي يستضيف مئات وحدات المعالجة العلمية والعددية، ولا غنى عنه لمن يعمل على مشاريع تحليل علمية، وأفضل وسيلة للوصول إلى تلك الحزم هو تثبيت إحدى إصدارات بايثون التي تحتوي على SciPy كإضافة قياسية فيها، مثل موقع anaconda.org، كما يحتوي sourceforge ومواقع التطوير مفتوح المصدر الأخرى على عدة مشاريع لبايثون فيها وحدات نافعة، ويمكنك الاستعانة بمحرك البحث نظرًا لتعدد المصادر، وتستطيع تصفحها وغيرها إذا أضفت كلمة python في بحثك، ولا تنس قراءة توثيق بايثون للمزيد من المعلومات عن البرمجة للإنترنت والرسوميات وبناء قواعد البيانات وغيرها.

والمهم هنا إدراك أن أغلب لغات البرمجة تحوي هذه الدوال، والوظائف الأساسية إما مضمّنة فيها أو تكون جزءًا من مكتبتها القياسية، فابحث أولًا في توثيقك قبل كتابة دالة ما، فربما تكون موجودةً ولا تحتاج إلى كتابتها.

تعريف الدوال الخاصة بنا

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

VBScript

سننشئ الآن دالةً تطبع مثال جدول الضرب الخاص بنا لأي قيمة نعطيها إليها كوسيط، ;ستبدو هذه الدالة في VBScript كما يلي:

<script type="text/vbscript">
Sub Times(N)
Dim I
For I = 1 To 12
    MsgBox I & " x " & N & " = " & I * N
Next 
End Sub
</script>

sنبدأ هنا في هذا المثال بالكلمة المفتاحية Sub -وهي اختصار برنامج فرعي Subroutine-، التي تلي محدد VBScript لكتل الشيفرات مباشرةً في السطر الثاني، ثم نعطيها قائمةً من المعامِلات بين قوسين.

أما الشيفرة التي داخل الكتلة المعرَّفة فهي شيفرة VBScript عادية مع استثناء أنها تعامل المعامِلات على أنها متغيرات محلية، لذا تسمى الدالة في المثال أعلاه Times، وتأخذ معامِلًا واحدًا هو N، كما تعرِّف المتغير المحلي I، ثم تنفذ حلقةً تكراريةً تعرض جدول الضرب الخاص بالعدد N باستخدام المتغيرين N و I، نستدعي هذه الدالة الجديدة كما يلي:

<script type="text/vbscript">
MsgBox "Here is the 7 times table..."
Times 7
</script>

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

كذلك فقد غلفنا المعامل N بقوسين أثناء تعريف الدالة، لكن هذا غير ضروري في VBScript عند استدعاء الدالة، كما قلنا من قبل.

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

<script type="text/vbscript">
Function TimesTable (N)
  Dim I, S
  S = N & " times table" & vbNewLine
  For I = 1 to 12
    S = S & I & " x " & N & " = " & I*N & vbNewLine
  Next
  TimesTable = S
End Function

Dim Multiplier
Multiplier = InputBox("Which table would you like?")
MsgBox TimesTable (Multiplier)
</script>

تكاد صيغة هذه الشيفرة أن تطابق صيغة Sub، مع استثناء أننا استخدمنا الكلمة Function بدلًا من Sub السابقة، ويجب أن نسند النتيجة إلى اسم الدالة داخل التعريف، لهذا ستعيد الدالة أي قيمة يحتويها اسمها عند خروجها:

  ...
  TimesTable = S
End Function

فإذا لم نسند قيمةً لاسم الدالة بشكل صريح، فستعيد الدالة القيمة الافتراضية، والتي تكون في الغالب صفرًا أو سلسلةً نصيةً فارغةً.

لاحظ أننا وضعنا أقواسًا حول الوسيط في سطر MsgBox، لأن MsgBox لن يعرف -لولا هذه الأقواس- هل يجب أن يُطبع Multiplier أم يُمرّره إلى الوسيط الأول والذي هو TimesTable، فلما وضعناه داخل الأقواس فهم المفسر أن القيمة هي وسيط للدالة TimesTable بدلًا من MsgBox.

بايثون

ستكون دالة جدول الضرب في بايثون كما يلي:

def times(n):
    for i in range(1,13):
        print( "%d x %d = %d" % (i, n, i*n) )

وتُستدعى كما يلي:

print( "Here is the 9 times table..." )
times(9)

لاحظ أن الإجراءات في بايثون لا تميَّز عن الدوال، ويُستخدم def لتعريفهما، والفرق الوحيد هو أن الدالة التي تعيد قيمةً تستخدم تعليمة return، كما يلي:

def timesTable(n):
   s = ""
   for i in range(1,13):
       s = s + "%d x %d = %d\n" % (i,n,n*i)
   return s

الأمر بسيط، إذ تعيد الدالة النتيجة باستخدام تعليمة return، أما إذا لم يكن لدينا تعليمة return صريحة فستعيد بايثون تلقائيًا قيمةً افتراضيةً تسمى None، والتي نتجاهلها في العادة. نستطيع الآن طباعة نتيجة الدالة كما يلي:

print( timesTable(7) )

يفضل عدم وضع تعليمات print داخل الدوال، وإنما جعل الدالة تعيد النتيجة باستخدام return، ثم طباعتها من خارج الدالة، ورغم أننا لم نتبع هذا الأسلوب في أمثلتنا، إلا أن هذا يجعل الدوال قابلةً لإعادة الاستخدام في نطاق أوسع من الحالات.

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

def firstEven(aList):
   for num in aList:
      if num % 2 == 0: # تحقق من كونه زوجيًا
         return num  # اخرج من الدالة فورًا
   return None   # لا نصل إليها إلا إذا لم نجد عددًا زوجيًا

ملاحظة بشأن المعاملات

قد يصعب على المبتدئين فهم دور المعامِلات في تعريف الدالة، فهل يجب تعريف الدالة كما يلي:

def f(x): # داخل الدالة x يمكن استخدام...

أم تعريفها بالشكل:

x = 42
def f(): # داخل الدالة x يمكن استخدام...

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

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

الوسيط الافتراضي

يشير هذا المصطلح إلى طريقة لتعريف معامِلات الدالة التي تأخذ قيمًا افتراضيةً إذا لم تمرَّر كوسطاء صراحةً، وأحد استخدامات هذا الوسيط المنطقية في دالة تعيد يوم الأسبوع، فإذا استدعيناها بدون قيمة فيكون قصدنا اليوم الحالي، وإلا فإننا نوفر رقم اليوم كوسيط، كما يلي:

import time

# None قيمة اليوم هي  
def dayOfWeek(DayNum = None):
    # طابق ترتيب اليوم مع قيم إعادة بايثون
    days = ['Saturday','Sunday',
            'Monday','Tuesday', 
            'Wednesday', 'Thursday', 'Friday']

    # تحقق من القيمة الافتراضية        
    if DayNum == None:
        theTime = time.localtime(time.time())
        DayNum = theTime[6] # استخرج قيمة اليوم
    return days[DayNum]

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

نستطيع الآن أن نستدعي ذلك كما يلي:

print( "Today is: %s" % dayOfWeek() )
Saturday.
print( "The third day is %s" % dayOfWeek(2) )

تذكر أننا نبدأ العد من الصفر في عالم الحواسيب، وأننا افترضنا أن اليوم الأول في الأسبوع هو السبت.

عد الكلمات

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

def numwords(s):
    s = s.strip() # أزل المحارف الزائدة
    list = s.split() # قائمة كل عنصر فيها يمثل كلمة
    return len(list) # عدد كلمات القائمة هو عدد كلمات s

لقد عرَّفنا الدالة في المثال أعلاه مستفيدين من بعض توابع السلاسل النصية المضمَّنة التي ذكرناها في مقال البيانات وأنواعها، ويمكن استخدام الدالة الآن كما يلي:

for line in file:
    total = total + numwords(line) # راكم مجموع كل سطر
print( "File had %d words" % total )

إذا حاولنا كتابة ذلك فلن تنجح الشيفرة، لأن مثل هذه الشيفرة تُعرف باسم الشيفرة الوهمية pseudocode، وهي تقنية تصميم شائعة للشيفرات لتوضيح الفكرة العامة لها، وليست مثالًا لشيفرة حقيقية صحيحة التركيب، وتُعرف أحيانًا باسم لغة وصف البرامج Program Description Language.

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

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

دوال جافاسكربت

يمكن إنشاء دوال داخل جافاسكربت باستخدام أمر function، كما يلي:

<script type="text/javascript">
var i, values;

function times(m) {
    var results = new Array();
    for (i = 1; i <= 12; i++) {
        results[i] = i * m;
        }
    return results;
}

// استخدم الدالة
values = times(8);

for (i=1;i<=12;i++){
    document.write(values[i] + "<br />");
    }
</script>

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

تنبيه

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

إنشاء الوحدات الخاصة

رأينا كيف ننشئ دوالًا خاصةً بنا وكيف نستدعيها من أجزاء أخرى من البرنامج، وهذا أمر جيد لأنه يوفر علينا كثيرًا من الكتابة المتكررة، ويجعل برامجنا سهلة الفهم، لأننا ننسى بعض التفاصيل بعد إنشاء دالة تخفيها، وهو مبدأ متبع في البرمجة عند الحاجة إلى إخفاء بعض التفاصيل، ويسمى إخفاء المعلومات، حيث تغلَّف المعلومات والتفاصيل في دالة ننشئها لها، لكن كيف نستخدم هذه الدوال في البرامج الأخرى؟ الإجابة على هذا هي إنشاء وحدة module لهذا الغرض.

وحدات بايثون

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

import sys

فإننا نخبر مفسر بايثون أن يقرأ هذه الوحدة وينفذ الشيفرة الموجودة فيها، ويتيح لنا الأسماء التي تولدها في ملفنا، ويبدو هذا كأننا ننشئ نسخةً من محتويات sys.py في برنامجنا، على أن بعض الوحدات مثل sys في البرمجة العملية ليس لها ملف sys.py أصلًا، لكننا سنتجاهل هذا الآن، وتوجد لغات برمجة مثل C و++C، التي ينسخ فيها المترجم أو المصرِّف ملفات الوحدة إلى البرنامج الحالي حسب الطلب.

ننشئ الوحدة بإنشاء ملف بايثون يحتوي الدوال التي نريد إعادة استخدامها في برامج أخرى، ثم نستورد الوحدة كما نستورد الوحدات القياسية، ولتنفيذ هذا عمليًا، انسخ الدالة التالية إلى ملف، واحفظه باسم timestab.py. يمكنك فعل هذا باستخدام IDLE أو Notepad، أو أي محرر آخر يحفظ الملفات النصية العادية، لكن لا تستخدم برامج معالجة نصوص مثل مايكروسوفت وورد، لأن تلك البرامج تدخل شيفرات تنسيق كثيرةً لن تفهمها بايثون.

def print_table(multiplier):
    print( "--- Printing the %d times table ---" % multiplier )
    for n in range(1,13):
        print( "%d x %d = %d" % (n, multiplier, n*multiplier) )

ثم اكتب في محث بايثون:

>>> import timestab
>>> timestab.print_table(12)

وهكذا تكون قد أنشأت وحدةً واستوردتها واستخدمت الدالة المعرَّفة فيها.

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

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

اقتباس

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

الوحدات في جافاسكربت وVBScript

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

اقتباس

لاحظ أن لغة Visual Basic -وهي الأخت الكبرى للغة VBScript-، فيها ذلك المفهوم الخاص بالوحدات، ويمكن تحميل وحدة من خلال بيئة تطوير متكاملة IDE من قائمة File، ثم خيار Open Module، وهناك بعض القيود لما يمكن فعله داخل وحدة Visual Basic، لكننا لن نتعرض لها بما أنها خارج نطاق حديثنا. توفر مايكروسوفت نسخةً مجانيةً من أحدث إصدار للغة VB Express، لكن يجب أن تسجل لديهم قبل أن تستخدمها. انظر في صفحتها إذا أردت التجربة بنفسك.

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

  <script type=text/JavaScript src="mymodule.js"></script>

نستطيع الوصول إلى جميع تعريفات الدوال الموجودة في ملف mymodule.js بمجرد إدراج السطر السابق داخل قسم <head> في صفحة الويب الخاصة بنا، وتوجد وحدات عديدة من الطرف الثالث متاحة لمبرمجي الويب ويمكن استيرادها بهذه الطريقة، ولعل أشهرها هي وحدة JQuery؛ أما في المواضع التي تُستخدم فيها جافاسكربت خارج المتصفحات -انظر قسم Windows Script Host اللاحق- فهناك آليات أخرى متاحة، ويُرجع في ذلك إلى التوثيق.

تقنية Windows Script Host

نظرنا حتى الآن إلى جافاسكربت وVBScript على أنهما لغات للبرمجة داخل الويب، ولكن توجد طريقة أخرى لاستخدامهما داخل بيئة ويندوز، وهو تقنية مضيف سكربت ويندوز Windows Script Host أو WSH اختصارًا، وهي تقنية أتاحتها مايكروسوفت لتمكين المستخدمين من برمجة حواسيبهم بنفس الطريقة التي استخدم بها مستخدمو نظام DOS قديمًا ملفات باتش Batch Files، فهي توفر آليات لقراءة الملفات والسجل والوصول إلى الحواسيب والطابعات التي في الشبكة وغيرها.

في الإصدار الثاني من WSH إمكانية تضمين ملف WSH آخر، ومن ثم توفير وحدات قابلة لإعادة الاستخدام، وذلك بإنشاء ملف وحدة أولًا اسمه SomeModule.vbs يحتوي على ما يلي:

Function SubtractTwo(N)
   SubtractTwo = N - 2
End function

ننشئ الآن ملف سكربت WSH اسمه testModule.wsf مثلًا، كما يلي:

<?xml version="1.0" encoding="UTF-8"?>

<job>
  <script type="text/vbscript" src="SomeModule.vbs" />
  <script type="text/vbscript">
      Dim value, result
      WScript.Echo "Type a number"
      value = WScript.StdIn.ReadLine
      result = SubtractTwo(CInt(value))

      WScript.Echo "The result was " & CStr(result)
  </script>
</job>

يمكن تشغيل هذا في ويندوز ببدء جلسة DOS وكتابة ما يلي:

C:\> cscript testModule.wsf

تسمى الطريقة التي تمت هيكلة ملف (wsf.) بها باسم XML، ويتواجد البرنامج داخل زوج من وسوم <job></job> بدلًا من وسم <html></html> الذي رأيناه في لغة HTML من قبل.

يشير أول وسم سكربت في الداخل إلى ملف وحدة اسمه SomeModule.vbs، أما وسم السكربت الثاني فيحتوي على برنامجنا الذي يصل إلى SubtractTwo داخل ملف SomeModule.vbs؛ بينما ملف ‎.vbs فيحتوي على شيفرة VBScript عادية ليس فيها وسوم XML أو HTML.

لاحظ أن علينا تهريب محرف & من أجل ضم السلاسل النصية لتعليمة WScript.Echo، لأن التعليمة جزء من ملف XML، وهذا المحرف مستخدم في لغة XML كرمز محدِّد.

نستخدم WScript.Stdin لقراءة مدخلات المستخدم، وهو تطبيق لما تحدثنا عنه في مقال قراءة البيانات من المستخدم.

تصلح هذه التقنية مع جافاسكربت أيضًا، أو لنكون أدق، مع نسخة مايكروسوفت من جافاسكربت التي تسمى JScript، وذلك بتغيير سمة type=‎، بل يمكن دمج اللغات في WSH بأن نستورد وحدةً مكتوبةً بجافاسكربت ونستخدمها في شيفرة VBScript أو العكس.

فيما يلي سكربت WSH مكافئ لما سبق، حيث تُستخدم جافاسكربت للوصول إلى وحدة من VBScript:

<?xml version="1.0" encoding="UTF-8"?>

<job>
  <script type="text/vbscript" src="SomeModule.vbs" />
  <script type="text/javascript">
      var value, result;
      WScript.Echo("Type a number");
      value = WScript.StdIn.ReadLine();
      result = SubtractTwo(parseInt(value));

      WScript.Echo("The result was " + result);
  </script>
</job>

نستطيع رؤية مدى تقارب هاتين النسختين، فإذا استثنينا بعض الأقواس الزائدة فسيمكننا القول أنهما متشابهتان للغاية؛ أما أغلب الأمور الفنية فتجري من خلال كائنات WScript.

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

خاتمة

نأمل أن تخرج من هذا المقال وقد تعلمت:

  • الدوال التي هي شكل من أشكال الوحدات.
  • تعيد الدوال قيمًا، أما الإجراءات فلا تعيد شيئًا.
  • تتكون الوحدات في بايثون في العادة من تعريفات للدوال داخل ملف.
  • يمكن إنشاء دوال جديدة في بايثون باستخدام الكلمة المفتاحية def.
  • استخدام Sub أوFunction في VBScript، وfunction في جافاسكربت.

ترجمة -بتصرف- للفصل الحادي عشر: Programming with Modules من كتاب Learning To Program لصاحبه Alan Gauld.

اقرأ أيضًا


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

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...