جدول المحتويات

  • وحدات بايثون: نظرة عامة
  • مسار بحث الوحدة النمطية
  • بيان الاستيراد
    استيراد <module_name>
    من <module_name> استيراد <الاسم (الأسماء)>
    من <module_name> استيراد <name> كـ <alt_name>
    استيراد <module_name> كـ <alt_name>
  • دالة دير ()
  • تنفيذ وحدة كبرنامج نصي
  • إعادة تحميل وحدة
  • حزم بايثون
  • تهيئة الحزمة
  • استيراد * من حزمة
  • الحزم الفرعية
  • خاتمة

تستكشف هذه المقالة وحدات Python وحزم Python ، وهما آليتان تسهّلان البرمجة المعيارية.

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

هناك العديد من المزايا لوحدة الكود في تطبيق كبير:

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

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

  • قابلية إعادة الاستخدام: يمكن إعادة استخدام الوظيفة المحددة في وحدة واحدة بسهولة (من خلال واجهة محددة بشكل مناسب) بواسطة أجزاء أخرى من التطبيق. هذا يلغي الحاجة إلى تكرار التعليمات البرمجية.

  • تحديد النطاق: تحدد الوحدات عادةً مساحة اسم منفصلة ، مما يساعد على تجنب الاصطدامات بين المعرفات في مناطق مختلفة من البرنامج. (أحد المبادئ في Zen of Python هو أن Namespaces هي فكرة رائعة – فلنفعل المزيد منها!)

الوظائف والوحدات والحزم كلها بنيات في بايثون تعزز تشكيل الكود.

وحدات بايثون: نظرة عامة

توجد في الواقع ثلاث طرق مختلفة لتعريف وحدة في بايثون:

  1. يمكن كتابة الوحدة في بايثون نفسها.
  2. يمكن كتابة وحدة في C وتحميلها ديناميكيًا في وقت التشغيل ، مثل وحدة re (التعبير العادي).
  3. توجد وحدة مضمنة بشكل جوهري في المترجم الفوري ، مثل وحدة itertools.

يتم الوصول إلى محتويات الوحدة بنفس الطريقة في جميع الحالات الثلاث: مع بيان الاستيراد.

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

على سبيل المثال ، افترض أنك قمت بإنشاء ملف يسمى mod.py يحتوي على ما يلي:

mod.py

s = "If Comrade Napoleon says it, it must be right."
a = [100, 200, 300]

def foo(arg):
    print(f'arg = {arg}')

class Foo:
    pass

يتم تحديد عدة كائنات في mod.py:

  • ق (سلسلة)
  • أ (قائمة)
  • foo () (دالة)
  • Foo (فصل دراسي)

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

>>> import mod
>>> print(mod.s)
If Comrade Napoleon says it, it must be right.
>>> mod.a
[100, 200, 300]
>>> mod.foo(['quux', 'corge', 'grault'])
arg = ['quux', 'corge', 'grault']
>>> x = mod.Foo()
>>> x
<mod.Foo object at 0x03C181F0>

مسار بحث الوحدة النمطية

متابعة للمثال أعلاه ، دعنا نلقي نظرة على ما يحدث عندما تنفذ بايثون العبارة:

import mod

عندما ينفذ المترجم بيان الاستيراد أعلاه ، فإنه يبحث عن mod.py في قائمة الأدلة المجمعة من المصادر التالية:

  • الدليل الذي تم تشغيل نص الإدخال منه أو الدليل الحالي إذا كان المترجم يعمل بشكل تفاعلي
  • قائمة الدلائل الموجودة في متغير بيئة PYTHONPATH ، إذا تم تعيينها. (يعتمد تنسيق PYTHONPATH على نظام التشغيل ولكن يجب أن يحاكي متغير بيئة PATH.)
  • قائمة أدلة تعتمد على التثبيت تم تكوينها في وقت تثبيت Python

يمكن الوصول إلى مسار البحث الناتج في متغير Python sys.path ، والذي يتم الحصول عليه من وحدة نمطية تسمى sys:

>>> import sys
>>> sys.path
['', 'C:\\Users\\john\\Documents\\Python\\doc', 'C:\\Python36\\Lib\\idlelib',
'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib',
'C:\\Python36', 'C:\\Python36\\lib\\site-packages']

ملاحظة: تعتمد المحتويات الدقيقة لـ sys.path على التثبيت. من شبه المؤكد أن ما سبق سيبدو مختلفًا قليلاً على جهاز الكمبيوتر الخاص بك.

وبالتالي ، للتأكد من العثور على الوحدة النمطية الخاصة بك ، عليك القيام بأحد الإجراءات التالية:

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

يوجد بالفعل خيار إضافي واحد: يمكنك وضع ملف الوحدة النمطية في أي دليل من اختيارك ثم تعديل sys.path في وقت التشغيل بحيث يحتوي على هذا الدليل. على سبيل المثال ، في هذه الحالة ، يمكنك وضع mod.py في الدليل C: \ Users \ john ثم إصدار العبارات التالية:

>>> sys.path.append(r'C:\Users\john')
>>> sys.path
['', 'C:\\Users\\john\\Documents\\Python\\doc', 'C:\\Python36\\Lib\\idlelib',
'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib',
'C:\\Python36', 'C:\\Python36\\lib\\site-packages', 'C:\\Users\\john']
>>> import mod

بمجرد استيراد الوحدة ، يمكنك تحديد الموقع الذي تم العثور عليها فيه باستخدام سمة __ملف__ للوحدة:

>>> import mod
>>> mod.__file__
'C:\\Users\\john\\mod.py'

>>> import re
>>> re.__file__
'C:\\Python36\\lib\\re.py'

يجب أن يكون جزء الدليل من __file__ أحد الأدلة في sys.path.

بيان الاستيراد

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

استيراد <module_name>

أبسط شكل هو الذي سبق عرضه أعلاه:

import <module_name>

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

العبارة import <module_name> تضع فقط <module_name> في جدول رموز المتصل. تظل الكائنات المحددة في الوحدة النمطية في جدول الرموز الخاص بالوحدة النمطية.

من المتصل ، يمكن الوصول إلى الكائنات الموجودة في الوحدة فقط عندما تكون مسبوقة بـ <module_name> عبر تدوين النقطة ، كما هو موضح أدناه.

بعد بيان الاستيراد التالي ، يتم وضع mod في جدول الرموز المحلي. وبالتالي ، فإن mod لها معنى في السياق المحلي للمتصل:

>>> import mod
>>> mod
<module 'mod' from 'C:\\Users\\john\\Documents\\Python\\doc\\mod.py'>

ولكن تظل s و foo في جدول الرموز الخاص بالوحدة وليست لها مغزى في السياق المحلي:

>>> s
NameError: name 's' is not defined
>>> foo('quux')
NameError: name 'foo' is not defined

ليتم الوصول إليها في السياق المحلي ، يجب أن تكون أسماء الكائنات المحددة في الوحدة مسبوقة بـ mod:

>>> mod.s
'If Comrade Napoleon says it, it must be right.'
>>> mod.foo('quux')
arg = quux

يمكن تحديد عدة وحدات مفصولة بفواصل في بيان استيراد واحد:

import <module_name>[, <module_name> ...]

من <module_name> استيراد <الاسم (الأسماء)>

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

from <module_name> import <name(s)>

بعد تنفيذ العبارة أعلاه ، يمكن الإشارة إلى <name (s)> في بيئة المتصل بدون البادئة <module_name>:

>>> from mod import s, foo
>>> s
'If Comrade Napoleon says it, it must be right.'
>>> foo('quux')
arg = quux

>>> from mod import Foo
>>> x = Foo()
>>> x
<mod.Foo object at 0x02E3AD50>

نظرًا لأن هذا النموذج من الاستيراد يضع أسماء الكائنات مباشرة في جدول رموز المتصل ، فسيتم استبدال أي كائنات موجودة بالفعل بنفس الاسم:

>>> a = ['foo', 'bar', 'baz']
>>> a
['foo', 'bar', 'baz']

>>> from mod import a
>>> a
[100, 200, 300]

حتى أنه من الممكن استيراد كل شيء بشكل عشوائي من وحدة بضربة واحدة:

from <module_name> import *

سيؤدي هذا إلى وضع أسماء جميع الكائنات من <module_name> في جدول الرموز المحلي ، باستثناء أي منها يبدأ بحرف التسطير السفلي (_).

فمثلا:

>>> from mod import *
>>> s
'If Comrade Napoleon says it, it must be right.'
>>> a
[100, 200, 300]
>>> foo
<function foo at 0x03B449C0>
>>> Foo
<class 'mod.Foo'>

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

من <module_name> استيراد <name> كـ <alt_name>

من الممكن أيضًا استيراد كائنات فردية ولكن إدخالها في جدول الرموز المحلي بأسماء بديلة:

from <module_name> import <name> as <alt_name>[, <name> as <alt_name> ]

هذا يجعل من الممكن وضع الأسماء مباشرة في جدول الرموز المحلي ولكن تجنب التعارض مع الأسماء الموجودة سابقًا:

>>> s = 'foo'
>>> a = ['foo', 'bar', 'baz']

>>> from mod import s as string, a as alist
>>> s
'foo'
>>> string
'If Comrade Napoleon says it, it must be right.'
>>> a
['foo', 'bar', 'baz']
>>> alist
[100, 200, 300]

استيراد <module_name> كـ <alt_name>

يمكنك أيضًا استيراد وحدة كاملة تحت اسم بديل:

import <module_name> as <alt_name>
>>> import mod as my_module
>>> my_module.a
[100, 200, 300]
>>> my_module.foo('qux')
arg = qux

يمكن استيراد محتويات الوحدة النمطية من داخل تعريف دالة. في هذه الحالة ، لا يتم الاستيراد حتى يتم استدعاء الوظيفة:

>>> def bar():
...     from mod import foo
...     foo('corge')
...

>>> bar()
arg = corge

ومع ذلك ، لا تسمح Python 3 ببناء جملة الاستيراد * العشوائي من داخل دالة:

>>> def bar():
...     from mod import *
...
SyntaxError: import * only allowed at module level

أخيرًا ، يمكن استخدام عبارة try مع بند باستثناء ImportError للحماية من محاولات الاستيراد غير الناجحة:

>>> try:
...     # Non-existent module
...     import baz
... except ImportError:
...     print('Module not found')
...

Module not found
>>> try:
...     # Existing module, but non-existent object
...     from mod import baz
... except ImportError:
...     print('Object not found in module')
...

Object not found in module

دالة دير ()

ترجع الدالة المضمنة dir () قائمة بالأسماء المعرفة في مساحة اسم. بدون وسيطات ، ينتج قائمة بالأسماء مرتبة أبجديًا في جدول الرموز المحلي الحالي:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> qux = [1, 2, 3, 4, 5]
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'qux']

>>> class Bar():
...     pass
...
>>> x = Bar()
>>> dir()
['Bar', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'qux', 'x']

لاحظ كيف أن الاستدعاء الأول لـ dir () أعلاه يسرد عدة أسماء تم تحديدها تلقائيًا وفي مساحة الاسم بالفعل عند بدء المترجم. نظرًا لتعريف الأسماء الجديدة (qux ، Bar ، x) ، فإنها تظهر في الاستدعاءات اللاحقة لـ dir ().

يمكن أن يكون هذا مفيدًا لتحديد ما تمت إضافته إلى مساحة الاسم بالضبط بواسطة عبارة الاستيراد:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> import mod
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'mod']
>>> mod.s
'If Comrade Napoleon says it, it must be right.'
>>> mod.foo([1, 2, 3])
arg = [1, 2, 3]

>>> from mod import a, Foo
>>> dir()
['Foo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'a', 'mod']
>>> a
[100, 200, 300]
>>> x = Foo()
>>> x
<mod.Foo object at 0x002EAD50>

>>> from mod import s as string
>>> dir()
['Foo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'a', 'mod', 'string', 'x']
>>> string
'If Comrade Napoleon says it, it must be right.'

عند إعطاء وسيطة هي اسم الوحدة ، يسرد dir () الأسماء المحددة في الوحدة:

>>> import mod
>>> dir(mod)
['Foo', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__spec__', 'a', 'foo', 's']
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
>>> from mod import *
>>> dir()
['Foo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'a', 'foo', 's']

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

أي ملف .py يحتوي على وحدة هو في الأساس برنامج نصي بلغة Python ، وليس هناك أي سبب لعدم تنفيذه مثل أحد.

هنا مرة أخرى هو mod.py كما تم تعريفه أعلاه:

mod.py

s = "If Comrade Napoleon says it, it must be right."
a = [100, 200, 300]

def foo(arg):
    print(f'arg = {arg}')

class Foo:
    pass

يمكن تشغيل هذا كبرنامج نصي:

C:\Users\john\Documents>python mod.py
C:\Users\john\Documents>

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

دعنا نعدل وحدة Python أعلاه بحيث تولد بعض المخرجات عند تشغيلها كبرنامج نصي:

mod.py

s = "If Comrade Napoleon says it, it must be right."
a = [100, 200, 300]

def foo(arg):
    print(f'arg = {arg}')

class Foo:
    pass

print(s)
print(a)
foo('quux')
x = Foo()
print(x)

الآن يجب أن يكون الأمر أكثر إثارة للاهتمام:

C:\Users\john\Documents>python mod.py
If Comrade Napoleon says it, it must be right.
[100, 200, 300]
arg = quux
<__main__.Foo object at 0x02F101D0>

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

>>> import mod
If Comrade Napoleon says it, it must be right.
[100, 200, 300]
arg = quux
<mod.Foo object at 0x0169AD50>

هذا هو على الارجح ليس ما تريد. ليس من المعتاد لوحدة أن تولد مخرجات عند استيرادها.

ألن يكون من الجيد التمييز بين وقت تحميل الملف كوحدة وبين تشغيله كبرنامج نصي مستقل؟

اسأل وسوف تأخذ.

عندما يتم استيراد ملف .py كوحدة نمطية ، تعيّن Python المتغير الغامض الخاص __name__ لاسم الوحدة. ومع ذلك ، إذا تم تشغيل ملف كبرنامج نصي مستقل ، فسيتم تعيين __name__ (بشكل إبداعي) على السلسلة “__main__”. باستخدام هذه الحقيقة ، يمكنك تمييز الحالة في وقت التشغيل وتغيير السلوك وفقًا لذلك:

mod.py

s = "If Comrade Napoleon says it, it must be right."
a = [100, 200, 300]

def foo(arg):
    print(f'arg = {arg}')

class Foo:
    pass

if (__name__ == '__main__'):
    print('Executing as standalone script')
    print(s)
    print(a)
    foo('quux')
    x = Foo()
    print(x)

الآن ، إذا قمت بتشغيل كبرنامج نصي ، فستحصل على الإخراج:

C:\Users\john\Documents>python mod.py
Executing as standalone script
If Comrade Napoleon says it, it must be right.
[100, 200, 300]
arg = quux
<__main__.Foo object at 0x03450690>

ولكن إذا قمت بالاستيراد كوحدة نمطية ، فلن تقوم بما يلي:

>>> import mod
>>> mod.foo('grault')
arg = grault

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

fact.py

def fact(n):
    return 1 if n == 1 else n * fact(n-1)

if (__name__ == '__main__'):
    import sys
    if len(sys.argv) > 1:
        print(fact(int(sys.argv[1])))

يمكن التعامل مع الملف كوحدة نمطية ، واستيراد وظيفة fact ():

>>> from fact import fact
>>> fact(6)
720

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

C:\Users\john\Documents>python fact.py 6
720

إعادة تحميل وحدة

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

ضع في اعتبارك ملف mod.py التالي:

mod.py

a = [100, 200, 300]
print('a =', a)
>>> import mod
a = [100, 200, 300]
>>> import mod
>>> import mod

>>> mod.a
[100, 200, 300]

لا يتم تنفيذ جملة print () على عمليات الاستيراد اللاحقة. (بالنسبة لهذه المسألة ، لا يوجد بيان الإسناد ، ولكن كما يظهر العرض النهائي لقيمة mod.a ، فإن هذا لا يهم. بمجرد إجراء المهمة ، فإنها تظل ثابتة.)

إذا قمت بإجراء تغيير على وحدة ما وتحتاج إلى إعادة تحميلها ، فأنت بحاجة إما إلى إعادة تشغيل المترجم الفوري أو استخدام وظيفة تسمى إعادة التحميل () من الوحدة importlib:

>>> import mod
a = [100, 200, 300]

>>> import mod

>>> import importlib
>>> importlib.reload(mod)
a = [100, 200, 300]
<module 'mod' from 'C:\\Users\\john\\Documents\\Python\\doc\\mod.py'>

حزم بايثون

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

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

يعد إنشاء حزمة أمرًا سهلاً للغاية ، حيث إنه يستخدم بنية الملفات الهرمية المتأصلة في نظام التشغيل. ضع في اعتبارك الترتيب التالي:

Image of a Python package

هنا ، يوجد دليل باسم pkg يحتوي على وحدتين ، mod1.py و mod2.py. محتويات الوحدات هي:

mod1.py

def foo():
    print('[mod1] foo()')

class Foo:
    pass

mod2.py

def bar():
    print('[mod2] bar()')

class Bar:
    pass

بالنظر إلى هذه البنية ، إذا كان دليل pkg موجودًا في مكان يمكن العثور عليه فيه (في أحد الأدلة الموجودة في sys.path) ، فيمكنك الرجوع إلى الوحدتين باستخدام تدوين النقطة (pkg.mod1 ، pkg.mod2) و قم باستيرادها بالصيغة التي تعرفها بالفعل:

import <module_name>[, <module_name> ...]
>>> import pkg.mod1, pkg.mod2
>>> pkg.mod1.foo()
[mod1] foo()
>>> x = pkg.mod2.Bar()
>>> x
<pkg.mod2.Bar object at 0x033F7290>
from <module_name> import <name(s)>
>>> from pkg.mod1 import foo
>>> foo()
[mod1] foo()
from <module_name> import <name> as <alt_name>
>>> from pkg.mod2 import Bar as Qux
>>> x = Qux()
>>> x
<pkg.mod2.Bar object at 0x036DFFD0>

يمكنك استيراد وحدات مع هذه العبارات أيضًا:

from <package_name> import <modules_name>[, <module_name> ...]
from <package_name> import <module_name> as <alt_name>
>>> from pkg import mod1
>>> mod1.foo()
[mod1] foo()

>>> from pkg import mod2 as quux
>>> quux.bar()
[mod2] bar()

يمكنك أيضًا استيراد الحزمة تقنيًا:

>>> import pkg
>>> pkg
<module 'pkg' (namespace)>

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

>>> pkg.mod1
Traceback (most recent call last):
  File "<pyshell#34>", line 1, in <module>
    pkg.mod1
AttributeError: module 'pkg' has no attribute 'mod1'
>>> pkg.mod1.foo()
Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    pkg.mod1.foo()
AttributeError: module 'pkg' has no attribute 'mod1'
>>> pkg.mod2.Bar()
Traceback (most recent call last):
  File "<pyshell#36>", line 1, in <module>
    pkg.mod2.Bar()
AttributeError: module 'pkg' has no attribute 'mod2'

لاستيراد الوحدات أو محتوياتها فعليًا ، تحتاج إلى استخدام أحد النماذج الموضحة أعلاه.

تهيئة الحزمة

إذا كان الملف المسمى __init__.py موجودًا في دليل الحزمة ، فسيتم استدعاؤه عند استيراد الحزمة أو الوحدة النمطية في الحزمة. يمكن استخدام هذا لتنفيذ كود تهيئة الحزمة ، مثل تهيئة بيانات مستوى الحزمة.

على سبيل المثال ، ضع في اعتبارك ملف __init__.py التالي:

__init__.py

print(f'Invoking __init__.py for {__name__}')
A = ['quux', 'corge', 'grault']

لنضيف هذا الملف إلى دليل pkg من المثال أعلاه:

Illustration of hierarchical file structure of Python packages

الآن عند استيراد الحزمة ، يتم تهيئة القائمة العامة A:

>>> import pkg
Invoking __init__.py for pkg
>>> pkg.A
['quux', 'corge', 'grault']

يمكن لوحدة نمطية في الحزمة الوصول إلى المتغير العام عن طريق استيرادها بدوره:

mod1.py

def foo():
    from pkg import A
    print('[mod1] foo() / A = ', A)

class Foo:
    pass
>>> from pkg import mod1
Invoking __init__.py for pkg
>>> mod1.foo()
[mod1] foo() / A =  ['quux', 'corge', 'grault']

يمكن أيضًا استخدام __init__.py لإحداث استيراد تلقائي للوحدات من الحزمة. على سبيل المثال ، رأيت سابقًا أن العبارة import pkg تضع الاسم pkg فقط في جدول الرموز المحلي للمتصل ولا تستورد أي وحدات. ولكن إذا كان __init__.py في دليل pkg يحتوي على ما يلي:

__init__.py

print(f'Invoking __init__.py for {__name__}')
import pkg.mod1, pkg.mod2

عند تنفيذ استيراد pkg ، يتم استيراد الوحدتين mod1 و mod2 تلقائيًا:

>>> import pkg
Invoking __init__.py for pkg
>>> pkg.mod1.foo()
[mod1] foo()
>>> pkg.mod2.bar()
[mod2] bar()

ملاحظة: تنص الكثير من وثائق Python على أن ملف __init__.py يجب أن يكون موجودًا في دليل الحزمة عند إنشاء حزمة. كان هذا صحيحًا في يوم من الأيام. اعتاد أن يكون وجود __init__.py يشير إلى Python أنه تم تعريف الحزمة. يمكن أن يحتوي الملف على رمز تهيئة أو قد يكون فارغًا ، ولكن يجب أن يكون موجودًا.

بدءًا من Python 3.3 ، تم تقديم حزم Namespace الضمنية. يسمح ذلك بإنشاء حزمة بدون أي ملف __init__.py. بالطبع ، يمكن أن يظل موجودًا إذا كانت هناك حاجة إلى تهيئة الحزمة. لكنها لم تعد مطلوبة.

استيراد * من حزمة

لأغراض المناقشة التالية ، يتم توسيع الحزمة المحددة مسبقًا لتشمل بعض الوحدات النمطية الإضافية:

Illustration of hierarchical file structure of Python packages

يوجد الآن أربع وحدات معرفة في دليل pkg. محتوياتها كما هو موضح أدناه:

mod1.py

def foo():
    print('[mod1] foo()')

class Foo:
    pass


mod2.py

def bar():
    print('[mod2] bar()')

class Bar:
    pass


mod3.py

def baz():
    print('[mod3] baz()')

class Baz:
    pass


mod4.py

def qux():
    print('[mod4] qux()')

class Qux:
    pass

(خيالي ، أليس كذلك؟)

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

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> from pkg.mod3 import *

>>> dir()
['Baz', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'baz']
>>> baz()
[mod3] baz()
>>> Baz
<class 'pkg.mod3.Baz'>

البيان المقابل للحزمة هو:

from <package_name> import *

ماذا يفعل ذلك؟

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> from pkg import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

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

بدلاً من ذلك ، تتبع Python هذا الاصطلاح: إذا كان الملف __init__.py في دليل الحزمة يحتوي على قائمة باسم __all__ ، فسيتم اعتبارها قائمة بالوحدات التي يجب استيرادها عند مواجهة العبارة من <package_name> import *.

في المثال الحالي ، افترض أنك أنشأت __init__.py في دليل pkg مثل هذا:

pkg / __ init__.py

__all__ = [
        'mod1',
        'mod2',
        'mod3',
        'mod4'
        ]

الآن من استيراد pkg * يستورد جميع الوحدات الأربع:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> from pkg import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'mod1', 'mod2', 'mod3', 'mod4']
>>> mod2.bar()
[mod2] bar()
>>> mod4.Qux
<class 'pkg.mod4.Qux'>

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

بالمناسبة ، يمكن تعريف __all__ في وحدة أيضًا ويخدم نفس الغرض: للتحكم في ما يتم استيراده مع الاستيراد *. على سبيل المثال ، قم بتعديل mod1.py على النحو التالي:

pkg / mod1.py

__all__ = ['foo']

def foo():
    print('[mod1] foo()')

class Foo:
    pass

الآن ستقوم عبارة import * من pkg.mod1 باستيراد ما هو وارد في __all__:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> from pkg.mod1 import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'foo']

>>> foo()
[mod1] foo()
>>> Foo
Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    Foo
NameError: name 'Foo' is not defined

foo () (الوظيفة) مُعرَّفة الآن في مساحة الاسم المحلية ، ولكن Foo (الفئة) ليست كذلك ، لأن الأخير ليس في __all__.

باختصار ، يتم استخدام __all__ بواسطة كلٍ من الحزم والوحدات للتحكم في ما يتم استيراده عند تحديد الاستيراد *. لكن السلوك الافتراضي يختلف:

  • بالنسبة للحزمة ، عندما لا يتم تعريف __all__ ، لا يستورد الاستيراد * أي شيء.
  • بالنسبة إلى وحدة نمطية ، عندما لا يتم تعريف __all__ ، يقوم الاستيراد * باستيراد كل شيء (باستثناء – كما خمنت – الأسماء التي تبدأ بشرطة سفلية).

الحزم الفرعية

يمكن أن تحتوي الحزم على حزم فرعية متداخلة إلى عمق عشوائي. على سبيل المثال ، دعونا نجري تعديلًا آخر على مثال دليل الحزمة على النحو التالي:

Illustration of hierarchical file structure of Python packages

تم تعريف الوحدات الأربعة (mod1.py و mod2.py و mod3.py و mod4.py) على النحو السابق. ولكن الآن ، بدلاً من تجميعها معًا في دليل pkg ، يتم تقسيمها إلى مجلدين فرعيين ، sub_pkg1 و sub_pkg2.

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

>>> import pkg.sub_pkg1.mod1
>>> pkg.sub_pkg1.mod1.foo()
[mod1] foo()

>>> from pkg.sub_pkg1 import mod2
>>> mod2.bar()
[mod2] bar()

>>> from pkg.sub_pkg2.mod3 import baz
>>> baz()
[mod3] baz()

>>> from pkg.sub_pkg2.mod4 import qux as grault
>>> grault()
[mod4] qux()

بالإضافة إلى ذلك ، يمكن للوحدة في حزمة فرعية واحدة أن تشير إلى كائنات في حزمة فرعية للأشقاء (في حالة احتواء الأخوة على بعض الوظائف التي تحتاجها). على سبيل المثال ، افترض أنك تريد استيراد وظيفة foo () وتنفيذها (محددة في الوحدة النمطية 1) من داخل الوحدة النمطية 3. يمكنك إما استخدام استيراد مطلق:

pkg / sub__pkg2 / mod3.py

def baz():
    print('[mod3] baz()')

class Baz:
    pass

from pkg.sub_pkg1.mod1 import foo
foo()
>>> from pkg.sub_pkg2 import mod3
[mod1] foo()
>>> mod3.foo()
[mod1] foo()

أو يمكنك استخدام استيراد نسبي ، حيث .. يشير إلى الحزمة ذات المستوى الأعلى. من داخل mod3.py ، الموجودة في الحزمة الفرعية sub_pkg2 ،

  • .. يقيّم الحزمة الأصلية (pkg) ، و
  • يتم تقييم ..sub_pkg1 إلى subpackage sub_pkg1 من الحزمة الرئيسية.

pkg / sub__pkg2 / mod3.py

def baz():
    print('[mod3] baz()')

class Baz:
    pass

from .. import sub_pkg1
print(sub_pkg1)

from ..sub_pkg1.mod1 import foo
foo()
>>> from pkg.sub_pkg2 import mod3
<module 'pkg.sub_pkg1' (namespace)>
[mod1] foo()

خاتمة

لقد غطيت في هذا البرنامج التعليمي المواضيع التالية:

  • كيفية إنشاء وحدة Python
  • المواقع التي يبحث فيها مترجم Python عن وحدة نمطية
  • كيفية الوصول إلى الكائنات المحددة في وحدة نمطية مع بيان الاستيراد
  • كيفية إنشاء وحدة قابلة للتنفيذ كبرنامج نصي مستقل
  • كيفية تنظيم الوحدات في حزم وحزم فرعية
  • كيفية التحكم في تهيئة الحزمة

نأمل أن يتيح لك ذلك فهمًا أفضل لكيفية الوصول إلى الوظائف المتاحة في العديد من الوحدات النمطية التابعة للجهات الخارجية والمدمجة المتوفرة في Python.

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

إذا كنت تريد معرفة المزيد ، فراجع الوثائق التالية على موقع Python.org:

  • نظام الاستيراد
  • دروس بايثون: الوحدات

بايثونينج سعيد!