الوحدات في بايثون

من موسوعة حسوب

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

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

تقدّم بايثون طريقة لإضافة التعريفات في ملف مستقل واستخدامها في شيفرة مستقلّة أو في مفسّر بايثون التفاعلي. تسمى هذه الملفات بالوحدات Modules. يمكن استيراد (import) التعريفات من وحدة إلى أخرى أو إلى الوحدة الرئيسة (main) والتي تضم مجموعة المتغيّرات التي يمكن الوصول إليها في شيفرة يجري تنفيذها في المستوى العالي أو في نمط الآلة الحاسبة.

إنشاء الوحدات في بايثون

الوحدة هي ملف يحتوي على تعريفات وعبارات بايثون، واسم الملف هو نفسه اسم الوحدة إضافة إلى اللاحقة ‎.py. يكون اسم الوحدة متوفّرًا داخل الوحدة (كسلسلة نصية) كقيمة للمتغيّر العام __name__.

يحتوي الملف fibo.py في المثال التالي على شيفرة تعريف دالة تنشئ متسلسلة فابيوناتشي إلى الحد n:

# Fibonacci numbers module
def fib(n):  # n كتابة متسلسلة فابيوناتشي إلى الحد
  a, b = 0, 1
  while b < n:
    print(b, end=' ')
    a, b = b, a+b
  print()
def fib2(n): # n إعادة متسلسلة فابيوناتشي إلى الحد
  result = []
  a, b = 0, 1
  while b < n:
    result.append(b)
    a, b = b, a+b
  return result

يمكن استيراد وحدة fibo إلى مفسّر بايثون باستخدام العبارة التالية:

>>> import fibo

لا تدخل العبارة السابقة أسماء الدوال المعرّفة في الوحدة fibo إلى جدول الرموز (Symbol table) الحالي بصورة مباشرة، وما يدخل إلى الجدول هو اسم الوحدة فقط. يمكن الوصول إلى تلك الدوال باستخدام اسم الوحدة وكما يلي:

>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'

يمكن إسناد الدالة إلى اسم محلّي في حال الرغبة باستخدامها على نحوٍ مكثّف:

>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

يمكن للوحدة أن تتضمن عبارات قابلة للتنفيذ إضافة إلى تعريفات الدوال، وتكون هذه العبارات معدّة لتهيئة (initialize) الوحدة، وتنفّذ فقط عند استخدام اسم الوحدة في عبارة import للمرة الأولى. (تنفّذ هذه العبارات إذا جرى تنفيذ الملف كشيفرة برمجية).

تمتلك كلّ وحدة جدول رموز خاصٍّ بها، وتستخدم جميع الدوال المعّرفة في الوحدة هذا الجدول كجدول رموز عام؛ لذا لن يحدث أي تضارب بين المتغيّرات العامة التي يعرّفها مُنشئ الوحدة وبين المتغيرات العامة التي يُنشئها المستخدم.

يمكن الوصول إلى المتغيّرات العامة في الوحدة باستخدام نفس الصياغة المتّبعة للوصول إلى دوالّها وهي: modname.itemname.

يمكن للوحدات أن تستورد وحدات أخرى، ومن الشائع (لكن ليس من اللازم) أن توضع جميع عبارات import في بداية الوحدة (أو الشيفرة). توضع أسماء الوحدات المستوردة في جدول الرموز العام الخاصّ بالوحدة المستورِدة. 

يمكن استيراد اسم معيّن من الوحدة بصورة مباشرة إلى جدول رموز الوحدة المتسورِدة وكما يلي:

>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

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

>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

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

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

يمكن ربط اسم خاص باسم الوحدة المستوردَة عن طريق استخدام الكلمة المفتاحية as:

>>> import fibo as fib
>>> fib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

وهكذا تصبح الوحدة المستوردَة متاحة باسم fib. يمكن استخدام as في عبارة from أيضًا:

>>> from fibo import fib as fibonacci 
>>> fibonacci(500) 
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

ملاحظة: لغرض رفع كفاءة الأداء تستورد كلّ وحدة إلى مفسّر بايثون مرة واحدة في كل جلسة؛ لذا إن طرأ أي تغيير أو تعديل على الوحدات يجب إعادة تشغيل المفسّر، أو يمكن اختبار وحدة واحدة على نحوٍ تفاعلي باستخدام الدالة importlib.reload()‎ وتمرير اسم الوحدة المراد اختبارها كمعامل لهذه الدالة. مثال:

import importlib
importlib.reload(modulename)

تنفيذ الوحدات كنصوص برمجية

يمكن تنفيذ الوحدة كنصّ برمجي باستخدام الأمر التالي في سطر الأوامر:

python fibo.py <arguments>

ينفّذ هذا الأمر الشيفرة التي تتضمنها الوحدة fibo.py كما لو تم استيرادها إلى المفسّر، ولكن تعيّن اللغة القيمة __main__ إلى المتغيّر العام __name__ في هذه الحالة. هذا يعني أنّه يمكن للوحدة أن تستخدم كنص برمجي وكوحدة قابلة للاستيراد بإضافة الشيفرة التالية إلى نهاية الوحدة، وذلك لأنّ الشيفرة التي تفسر سطر الأوامر لن تعمل إلا إذا كانت الوحدّة منفّذة كملف رئيسي "main":

$ python fibo.py 50
1 1 2 3 5 8 13 21 34

لا يؤدي استيراد الوحدة إلى تنفيذ الشيفرة:

>>> import fibo
>>>

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

عند استيراد وحدة باسم spam على سبيل المثال، يبدأ مفسر بايثون بالبحث عن وحدة داخلية (built-in، أي مُضمَّنة في اللغة) تحمل هذا الاسم، وإن أخفق في العثور على واحدة يبحث المفسّر عن أيّ ملف يحمل الاسم spam.py في قائمة من المجلدات مخزّنة في المتغيّر sys.path.

تهيّئ اللغة قيمة المتغيّر sys.path من المواقع التالية:

  • المجلّد الحاوي على ملف الشيفرة (أو المجلد الحالي في حال عدم تعيين أي ملف).
  • PYTHONPATH وهو قائمة من أسماء المجلدات تحمل نفس صيغة المتغير PATH الخاص بالصدفة).
  • القيم الافتراضية عند تثبيت بايثون.

ملاحظة: في الأنظمة التي تدعم الوصلات الرمزية (symbolic links)، تُحدِّد اللغة المجلّد الحاوي لملف الشيفرة البرمجية بعد أن تتبع الوصلة الرميزة. وبعبارة أخرى لن يضاف المجلّد الذي يحتوي على وصلةٍ رمزيةٍ إلى مسار الوحدة.

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

ملفات بايثون المصرّفة

لزيادة سرعة عملية تحميل الوحدات تخبّئ بايثون نسخة مصرّفة من كلّ وحدة في مجلد يحمل الاسم __pycache__ وتحمل كل نسخة الاسم module.version.pyc، ويرمز version هنا إلى تنسيق الملف المصرَّف، وعادة ما يكون رقم إصدار بايثون. 

على سبيل المثال، تخبّئ بايثون الملف spam.py في المجلد ‎__pycache__/spam.cpython-33.pyc‎ عند استخدام الإصدار 3.3 من CPython. تسمح طريقة التسمية هذه بوجود وحدات مصرّفة من إصدارات ونسخ مختلفة.

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

إلى جانب أنّ الوحدات المصرّفة لا تعتمد على نوع المنصّة التي تعمل عليها؛ لهذا يمكن مشاركة المكتبة ذاتها بين أنظمة مختلفة وبمعماريات مختلفة.

لا تتحقّق بايثون من النسخ المخبّئة في حالتين اثنتين:

  1. تعيد بايثون دائمًا تصريف الوحدات المحمّلة مباشرة من سطر الأوامر ولا تحفظ نتائج إعادة التصريف.
  2. لا تتحقّق بايثون من النسخ المخبّئة في حال غياب أي وحدة مصدرية. ولتدعم بايثون توزيعة غير مصدرية (مصرّفة فقط) يجب أن تكون الوحدة المصرّفة في المجلد المصدري ويجب أن لا يكون هناك أي وحدة مصدرية.

ملاحظات

  • يمكن استخدام المفتاحين ‎-O و ‎-OO مع أمر python في سطر الأوامر لتقليص حجم الوحدات المصرّفة. يحذف المفتاح ‎-O عبارات assert، ويحذف المفتاح ‎-OO عبارات assert و سلاسل __doc__ النصية. تعتمد بعض البرامج على هذه العبارات؛ لذا يجب توخّي الحذر عند استخدام هذين المفتاحين. تحمل النسخة المحسّنة الوسم opt-‎ وتكون أصغر حجمًا في العادة من الوحدات المصرّفة الأصلية. قد تتغير عملية التحسين في الإصدارات المستقبلية.
  • لا فرق في سرعة القراءة من ملف ‎.pyc مقارنة بملف ‎.py، ولكن تمتاز ملفات ‎.pyc بسرعة أعلى في التحميل.
  • يمكن للوحدة compileall أن تنشئ ملفات ‎.pyc لجميع الوحدات الموجودة في مجلّد معين.

مصادر

  • صفحة Modules في توثيق بايثون الرسمي.