Python پایتون ۳.۱۴
فصل ۶

ماژول‌ها (Modules)

📖 ۱۵ دقیقه 🎯 متوسط 🔄 ۲۰۲۶

اگر از مفسر پایتون خارج شوید و دوباره وارد شوید، تعاریفی که ایجاد کرده‌اید (توابع و متغیرها) از بین می‌روند. بنابراین، اگر می‌خواهید برنامه‌ای نسبتاً بلندتر بنویسید، بهتر است از یک ویرایشگر متن برای آماده کردن ورودی برای مفسر استفاده کنید و آن را با آن فایل به‌عنوان ورودی اجرا کنید. این کار به‌عنوان ایجاد یک اسکریپت (script) شناخته می‌شود. با طولانی‌تر شدن برنامه‌تان، ممکن است بخواهید آن را به چندین فایل تقسیم کنید تا نگهداری آسان‌تر شود. همچنین ممکن است بخواهید از یک تابع مفید که در چندین برنامه نوشته‌اید استفاده کنید بدون اینکه تعریف آن را در هر برنامه کپی کنید.

برای پشتیبانی از این موضوع، پایتون روشی دارد که تعاریف را در یک فایل قرار دهید و از آن‌ها در یک اسکریپت یا در یک نمونهٔ تعاملی (interactive) از مفسر استفاده کنید. چنین فایلی یک ماژول (module) نامیده می‌شود؛ تعاریف موجود در یک ماژول می‌توانند به ماژول‌های دیگر یا به ماژول اصلی (main module) وارد شوند (مجموعهٔ متغیرهایی که در یک اسکریپت اجرا شده در سطح بالا (top level) و در حالت ماشین‌حساب به آن‌ها دسترسی دارید).

یک ماژول فایلی است که شامل تعاریف و دستورات پایتون است. نام فایل، نام ماژول با پسوند .py appended است. در داخل یک ماژول، نام ماژول (به‌عنوان یک رشته) به‌عنوان مقدار متغیر سراسری __name__ در دسترس است. برای مثال، از ویرایشگر متن مورد علاقهٔ خود استفاده کنید تا فایلی به نام fibo.py در دایرکتوری جاری با محتویات زیر ایجاد کنید:

# Fibonacci numbers module
def fib(n):
    """Write Fibonacci series up to n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b
    print()


def fib2(n):
    """Return Fibonacci series up to n."""
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a + b
    return result

حالا مفسر پایتون را وارد کرده و این ماژول را با دستور زیر import کنید:

>>> import fibo

این کار نام توابع تعریف‌شده در fibo را مستقیماً به فضای نام (namespace) جاری اضافه نمی‌کند (برای جزئیات بیشتر به Python Scopes and Namespaces مراجعه کنید)؛ فقط نام ماژول fibo را به آنجا اضافه می‌کند. با استفاده از نام ماژول می‌توانید به توابع دسترسی پیدا کنید:

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

اگر قصد دارید اغلب از یک تابع استفاده کنید، می‌توانید آن را به یک نام محلی (local name) نسبت دهید:

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

۶.۱. بیشتر دربارهٔ ماژول‌ها

یک ماژول می‌تواند شامل دستورات قابل‌اجرا (executable statements) و همچنین تعاریف توابع باشد. این دستورات برای مقداردهی اولیه (initialize) ماژول در نظر گرفته شده‌اند. آن‌ها فقط اولین باری که نام ماژول در یک دستور import مشاهده می‌شود، اجرا می‌شوند. [1] (همچنین اگر فایل به‌عنوان یک اسکریپت اجرا شود، اجرا می‌شوند.)

هر ماژول فضای نام خصوصی (private namespace) خود را دارد که به‌عنوان فضای نام سراسری (global namespace) توسط تمام توابع تعریف‌شده در ماژول استفاده می‌شود. بنابراین، نویسندهٔ یک ماژول می‌تواند از متغیرهای سراسری در ماژول بدون نگرانی از برخورد تصادفی با متغیرهای سراسری کاربر استفاده کند. از طرف دیگر، اگر بدانید چه کاری انجام می‌دهید، می‌توانید به متغیرهای سراسری یک ماژول با همان نمادی که برای ارجاع به توابع آن استفاده می‌شود، دسترسی پیدا کنید: modname.itemname.

ماژول‌ها می‌توانند ماژول‌های دیگر را import کنند. قرار دادن تمام دستورات import در ابتدای یک ماژول (یا اسکریپت، برای این matter) مرسوم است اما الزامی نیست. نام‌های ماژول وارد شده، اگر در سطح بالای یک ماژول (خارج از هر تابع یا کلاسی) قرار داده شوند، به فضای نام سراسری ماژول اضافه می‌شوند.

یک نوع از دستور import وجود دارد که نام‌ها را از یک ماژول به‌طور مستقیم به فضای نام ماژول واردکننده import می‌کند. برای مثال:

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

این کار نام ماژولی که import از آن انجام شده است را در فضای نام محلی معرفی نمی‌کند (بنابراین در مثال، fibo تعریف نشده است).

حتی یک نوع برای import کردن تمام نام‌هایی که یک ماژول تعریف می‌کند وجود دارد:

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

این کار تمام نام‌ها به جز آن‌هایی که با زیرخط (_) شروع می‌شوند را import می‌کند. در بیشتر موارد، برنامه‌نویسان پایتون از این قابلیت استفاده نمی‌کنند زیرا مجموعه‌ای ناشناخته از نام‌ها را به مفسر وارد می‌کند و احتمالاً برخی چیزهایی را که قبلاً تعریف کرده‌اید پنهان می‌کند.

توجه داشته باشید که به‌طور کلی، عمل import * از یک ماژول یا بسته (package) مورد پسند نیست، زیرا اغلب باعث ایجاد کد با خوانایی ضعیف می‌شود. با این حال، استفاده از آن برای صرفه‌جویی در تایپ در جلسات تعاملی (interactive sessions) اشکالی ندارد.

اگر نام ماژول با as دنبال شود، آنگاه نامی که بعد از as می‌آید مستقیماً به ماژول وارد شده متصل می‌شود.

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

این کار عملاً ماژول را به همان روشی import می‌کند که import fibo انجام می‌دهد، با تنها تفاوت اینکه به‌عنوان fib در دسترس است.

همچنین می‌توان از آن هنگام استفاده از from با اثرات مشابه استفاده کرد:

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

توجه: به دلایل کارایی، هر ماژول فقط یک بار در هر جلسهٔ مفسر import می‌شود. بنابراین، اگر ماژول‌های خود را تغییر دهید، باید مفسر را مجدداً راه‌اندازی کنید — یا، اگر فقط یک ماژول است که می‌خواهید به‌صورت تعاملی تست کنید، از importlib.reload() استفاده کنید، به‌عنوان مثال: import importlib; importlib.reload(modulename).

۶.۱.۱. اجرای ماژول‌ها به‌عنوان اسکریپت

وقتی یک ماژول پایتون را با دستور زیر اجرا می‌کنید:

python fibo.py <arguments>

کد موجود در ماژول اجرا می‌شود، درست مانند زمانی که آن را import می‌کنید، اما با این تفاوت که __name__ روی "__main__" تنظیم می‌شود. این بدان معناست که با اضافه کردن این کد در انتهای ماژول خود:

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

می‌توانید فایل را هم به‌عنوان یک اسکریپت و هم به‌عنوان یک ماژول قابل import استفاده کنید، زیرا کدی که خط فرمان را تجزیه می‌کند فقط زمانی اجرا می‌شود که ماژول به‌عنوان فایل "اصلی" (main) اجرا شود:

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

اگر ماژول import شود، کد اجرا نمی‌شود:

>>> import fibo

این کار اغلب یا برای ارائهٔ یک رابط کاربری مناسب برای یک ماژول، یا برای اهداف تست (اجرای ماژول به‌عنوان اسکریپت، یک مجموعه تست را اجرا می‌کند) استفاده می‌شود.

۶.۱.۲. مسیر جست‌وجوی ماژول (Module Search Path)

وقتی یک ماژول به نام spam import می‌شود، مفسر ابتدا به دنبال یک ماژول توکار (built-in module) با آن نام می‌گردد. این نام‌های ماژول در sys.builtin_module_names فهرست شده‌اند. اگر پیدا نشد، سپس به دنبال فایلی به نام spam.py در لیستی از دایرکتوری‌هایی که توسط متغیر sys.path داده شده است، می‌گردد. sys.path از این مکان‌ها مقداردهی اولیه می‌شود:

  • دایرکتوری حاوی اسکریپت ورودی (یا دایرکتوری جاری وقتی هیچ فایلی مشخص نشده است).
  • PYTHONPATH (لیستی از نام‌های دایرکتوری، با همان نحو متغیر shell PATH).
  • مقدار پیش‌فرض وابسته به نصب (به‌طور معمول شامل یک دایرکتوری site-packages است که توسط ماژول site مدیریت می‌شود).

جزئیات بیشتر در The initialization of the sys.path module search path موجود است.

توجه: در سیستم‌های فایلی که از symlink پشتیبانی می‌کنند، دایرکتوری حاوی اسکریپت ورودی پس از دنبال کردن symlink محاسبه می‌شود. به عبارت دیگر، دایرکتوری حاوی symlink به مسیر جست‌وجوی ماژول اضافه نمی‌شود.

پس از مقداردهی اولیه، برنامه‌های پایتون می‌توانند sys.path را تغییر دهند. دایرکتوری حاوی اسکریپتی که در حال اجرا است، در ابتدای مسیر جست‌وجو، جلوتر از مسیر کتابخانهٔ استاندارد قرار می‌گیرد. این بدان معناست که اسکریپت‌های موجود در آن دایرکتوری به جای ماژول‌های هم‌نام در دایرکتوری کتابخانه بارگذاری می‌شوند. این یک خطا است مگر اینکه جایگزینی عمدی باشد. برای اطلاعات بیشتر به بخش Standard Modules مراجعه کنید.

۶.۱.۳. فایل‌های "کامپایل‌شده" پایتون

برای سرعت بخشیدن به بارگذاری ماژول‌ها، پایتون نسخهٔ کامپایل‌شدهٔ هر ماژول را در دایرکتوری __pycache__ با نام module.version.pyc ذخیره می‌کند، جایی که version فرمت فایل کامپایل‌شده را کدگذاری می‌کند؛ معمولاً شامل شماره نسخهٔ پایتون است. برای مثال، در CPython نسخهٔ ۳.۳، نسخهٔ کامپایل‌شدهٔ spam.py به‌صورت __pycache__/spam.cpython-33.pyc ذخیره می‌شد. این قرارداد نام‌گذاری اجازه می‌دهد تا ماژول‌های کامپایل‌شده از انتشارهای مختلف و نسخه‌های مختلف پایتون با هم هم‌زیستی (coexist) داشته باشند.

پایتون تاریخ اصلاح (modification date) منبع را با نسخهٔ کامپایل‌شده مقایسه می‌کند تا ببیند آیا منسوخ شده و نیاز به کامپایل مجدد دارد یا خیر. این یک فرآیند کاملاً خودکار است. همچنین، ماژول‌های کامپایل‌شده مستقل از پلتفرم هستند، بنابراین می‌توان کتابخانهٔ یکسان را در سیستم‌هایی با معماری‌های مختلف به اشتراک گذاشت.

پایتون در دو شرایط کش (cache) را بررسی نمی‌کند. اول، همیشه دوباره کامپایل می‌کند و نتیجه را برای ماژولی که مستقیماً از خط فرمان بارگذاری می‌شود ذخیره نمی‌کند. دوم، اگر ماژول منبعی وجود نداشته باشد، کش را بررسی نمی‌کند. برای پشتیبانی از توزیع غیرمنبع (فقط کامپایل‌شده)، ماژول کامپایل‌شده باید در دایرکتوری منبع باشد و نباید ماژول منبعی وجود داشته باشد.

چند نکته برای متخصصان:

  • می‌توانید از سوئیچ‌های -O یا -OO در خط فرمان پایتون برای کاهش حجم یک ماژول کامپایل‌شده استفاده کنید. سوئیچ -O دستورات assert را حذف می‌کند، سوئیچ -OO هر دو دستورات assert و رشته‌های __doc__ را حذف می‌کند. از آنجا که برخی برنامه‌ها ممکن است به در دسترس بودن این‌ها وابسته باشند، فقط در صورتی باید از این گزینه استفاده کنید که بدانید چه کاری انجام می‌دهید. ماژول‌های "بهینه‌شده" دارای برچسب opt- هستند و معمولاً کوچک‌ترند. انتشارهای آینده ممکن است اثرات بهینه‌سازی را تغییر دهند.
  • یک برنامه وقتی از یک فایل .pyc خوانده می‌شود سریع‌تر از زمانی که از یک فایل .py خوانده می‌شود، اجرا نمی‌شود؛ تنها چیزی که در مورد فایل‌های .pyc سریع‌تر است، سرعت بارگذاری آن‌هاست.
  • ماژول compileall می‌تواند فایل‌های .pyc را برای تمام ماژول‌های موجود در یک دایرکتوری ایجاد کند.

جزئیات بیشتر در مورد این فرآیند، از جمله نمودار جریان تصمیم‌گیری‌ها، در PEP 3147 موجود است.

۶.۲. ماژول‌های استاندارد (Standard Modules)

پایتون با یک کتابخانه از ماژول‌های استاندارد ارائه می‌شود که در یک سند جداگانه، Python Library Reference (از این پس "Library Reference") توضیح داده شده است. برخی از ماژول‌ها در مفسر ساخته شده‌اند (built into the interpreter)؛ این‌ها دسترسی به عملیاتی را فراهم می‌کنند که بخشی از هستهٔ زبان نیستند اما با این وجود ساخته شده‌اند، یا برای کارایی یا برای ارائهٔ دسترسی به primitives سیستم‌عامل مانند فراخوانی‌های سیستمی (system calls). مجموعهٔ چنین ماژول‌هایی یک گزینهٔ پیکربندی است که به پلتفرم زیرین نیز بستگی دارد. برای مثال، ماژول winreg فقط در سیستم‌های Windows ارائه می‌شود. یک ماژول خاص شایستهٔ توجه است: sys که در هر مفسر پایتون ساخته شده است. متغیرهای sys.ps1 و sys.ps2 رشته‌های مورد استفاده به‌عنوان نشانه‌های اولیه و ثانویه (prompts) را تعریف می‌کنند:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>

این دو متغیر فقط در صورتی تعریف می‌شوند که مفسر در حالت تعاملی (interactive mode) باشد.

متغیر sys.path لیستی از رشته‌ها است که مسیر جست‌وجوی مفسر برای ماژول‌ها را تعیین می‌کند. این متغیر به یک مسیر پیش‌فرض که از متغیر محیطی PYTHONPATH گرفته شده است، یا از یک مقدار پیش‌فرض توکار اگر PYTHONPATH تنظیم نشده باشد، مقداردهی اولیه می‌شود. می‌توانید آن را با استفاده از عملیات استاندارد لیست تغییر دهید:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

۶.۳. تابع dir()

از تابع توکار dir() برای پیدا کردن نام‌هایی که یک ماژول تعریف می‌کند، استفاده می‌شود. این تابع یک لیست مرتب‌شده از رشته‌ها را برمی‌گرداند:

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
 '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
 '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
 '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
 '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
 'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
 'warnoptions']

بدون آرگومان، dir() نام‌هایی را که در حال حاضر تعریف کرده‌اید فهرست می‌کند:

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

توجه داشته باشید که این تابع تمام انواع نام‌ها را فهرست می‌کند: متغیرها، ماژول‌ها، توابع و غیره.

dir() نام‌های توابع و متغیرهای توکار (built-in) را فهرست نمی‌کند. اگر لیستی از آن‌ها را می‌خواهید، آن‌ها در ماژول استاندارد builtins تعریف شده‌اند:

>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
 'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
 'NotImplementedError', 'OSError', 'OverflowError',
 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
 '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
 'zip']

۶.۴. بسته‌ها (Packages)

بسته‌ها روشی برای ساختاردهی فضای نام ماژول پایتون با استفاده از "نام‌های ماژول نقطه‌دار" (dotted module names) هستند. برای مثال، نام ماژول A.B یک زیرماژول (submodule) به نام B را در یک بسته به نام A مشخص می‌کند. درست مانند استفاده از ماژول‌ها که نویسندگان ماژول‌های مختلف را از نگرانی در مورد نام‌های متغیر سراسری یکدیگر نجات می‌دهد، استفاده از نام‌های ماژول نقطه‌دار، نویسندگان بسته‌های چندماژولی مانند NumPy یا Pillow را از نگرانی در مورد نام‌های ماژول یکدیگر نجات می‌دهد.

فرض کنید می‌خواهید مجموعه‌ای از ماژول‌ها (یک "بسته") برای مدیریت یکنواخت فایل‌های صوتی و داده‌های صوتی طراحی کنید. فرمت‌های مختلف فایل صوتی زیادی وجود دارد (معمولاً با پسوند آن‌ها شناخته می‌شوند، برای مثال: .wav، .aiff، .au)، بنابراین ممکن است نیاز به ایجاد و نگهداری مجموعه‌ای رو به رشد از ماژول‌ها برای تبدیل بین فرمت‌های مختلف فایل داشته باشید. همچنین عملیات مختلف زیادی وجود دارد که ممکن است بخواهید روی داده‌های صوتی انجام دهید (مانند میکس، اضافه کردن اکو، اعمال تابع یکسان‌کننده (equalizer)، ایجاد یک افکت استریوی مصنوعی)، بنابراین علاوه بر این، ماژول‌های بی‌نهایتی برای انجام این عملیات خواهید نوشت. در اینجا یک ساختار احتمالی برای بستهٔ شما (که بر حسب یک سیستم فایل سلسله‌مراتبی بیان شده است) آورده شده است:

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

هنگام import کردن بسته، پایتون دایرکتوری‌های موجود در sys.path را برای جست‌وجوی زیردایرکتوری بسته جست‌وجو می‌کند.

فایل‌های __init__.py برای اینکه پایتون دایرکتوری‌های حاوی فایل را به‌عنوان بسته در نظر بگیرد، لازم هستند (مگر اینکه از namespace package استفاده کنید، که یک ویژگی نسبتاً پیشرفته است). این کار از دایرکتوری‌هایی با نام رایج، مانند string، جلوگیری می‌کند که به‌طور ناخواسته ماژول‌های معتبری را که بعداً در مسیر جست‌وجوی ماژول ظاهر می‌شوند، پنهان کنند. در ساده‌ترین حالت، __init__.py می‌تواند فقط یک فایل خالی باشد، اما همچنین می‌تواند کد مقداردهی اولیه برای بسته را اجرا کند یا متغیر __all__ را که بعداً توضیح داده می‌شود، تنظیم کند.

کاربران بسته می‌توانند ماژول‌های جداگانه را از بسته import کنند، برای مثال:

>>> import sound.effects.echo

این کار زیرماژول sound.effects.echo را بارگذاری می‌کند. باید با نام کامل آن ارجاع داده شود.

>>> sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

روش جایگزین برای import کردن زیرماژول این است:

>>> from sound.effects import echo

این کار نیز زیرماژول echo را بارگذاری می‌کند و آن را بدون پیشوند بسته در دسترس قرار می‌دهد، بنابراین می‌توان از آن به‌صورت زیر استفاده کرد:

>>> echo.echofilter(input, output, delay=0.7, atten=4)

تغییر دیگری این است که تابع یا متغیر مورد نظر را مستقیماً import کنید:

>>> from sound.effects.echo import echofilter

باز هم، این کار زیرماژول echo را بارگذاری می‌کند، اما این باعث می‌شود تابع echofilter() آن به‌طور مستقیم در دسترس باشد:

>>> echofilter(input, output, delay=0.7, atten=4)

توجه داشته باشید که هنگام استفاده از from package import item، item می‌تواند یک زیرماژول (یا زیربسته) از بسته باشد، یا نام دیگری که در بسته تعریف شده است، مانند یک تابع، کلاس یا متغیر. دستور import ابتدا بررسی می‌کند که آیا item در بسته تعریف شده است یا خیر؛ اگر نه، فرض می‌کند که یک ماژول است و سعی می‌کند آن را بارگذاری کند. اگر نتواند آن را پیدا کند، یک استثنای ImportError ایجاد می‌کند.

برعکس، هنگام استفاده از نحوی مانند import item.subitem.subsubitem، هر آیتم به جز آخرین مورد باید یک بسته باشد؛ آخرین آیتم می‌تواند یک ماژول یا یک بسته باشد اما نمی‌تواند یک کلاس یا تابع یا متغیری باشد که در آیتم قبلی تعریف شده است.

۶.۴.۱. Import * از یک بسته

حالا وقتی کاربر می‌نویسد from sound.effects import * چه اتفاقی می‌افتد؟ در حالت ایده‌آل، امیدواریم که این کار به نوعی به سیستم فایل برود، پیدا کند که کدام زیرماژول‌ها در بسته وجود دارند، و همهٔ آن‌ها را import کند. این کار ممکن است زمان زیادی ببرد و import کردن زیرماژول‌ها ممکن است عوارض جانبی ناخواسته‌ای داشته باشد که فقط زمانی باید رخ دهند که زیرماژول به‌صراحت import شود.

تنها راه‌حل این است که نویسندهٔ بسته یک فهرست صریح از بسته ارائه دهد. دستور import از قرارداد زیر استفاده می‌کند: اگر کد __init__.py یک بسته لیستی به نام __all__ تعریف کند، آنگاه به‌عنوان لیستی از نام‌های ماژولی در نظر گرفته می‌شود که باید زمانی که from package import * مواجه می‌شود، import شوند. بر عهدهٔ نویسندهٔ بسته است که این لیست را زمانی که نسخهٔ جدیدی از بسته منتشر می‌شود، به‌روز نگه دارد. نویسندگان بسته همچنین ممکن است تصمیم بگیرند که از آن پشتیبانی نکنند، اگر استفاده‌ای برای import * از بستهٔ خود نمی‌بینند. برای مثال، فایل sound/effects/__init__.py می‌تواند شامل کد زیر باشد:

__all__ = ["echo", "surround", "reverse"]

این بدان معناست که from sound.effects import * سه زیرماژول نام‌برده از بستهٔ sound.effects را import می‌کند.

توجه داشته باشید که زیرماژول‌ها ممکن است توسط نام‌های تعریف‌شدهٔ محلی (locally defined names) پنهان شوند. برای مثال، اگر یک تابع reverse به فایل sound/effects/__init__.py اضافه کنید، from sound.effects import * فقط دو زیرماژول echo و surround را import می‌کند، اما زیرماژول reverse را نه، زیرا توسط تابع reverse تعریف‌شدهٔ محلی پنهان می‌شود:

__all__ = [
    "echo",      # refers to the 'echo.py' file
    "surround",  # refers to the 'surround.py' file
    "reverse",   # !!! refers to the 'reverse' function now !!!
]


def reverse(msg: str):  # <-- this name shadows the 'reverse.py' submodule
    return msg[::-1]    # in the case of a 'from sound.effects import *'

اگر __all__ تعریف نشده باشد، دستور from sound.effects import * تمام زیرماژول‌ها را از بستهٔ sound.effects به فضای نام جاری import نمی‌کند؛ فقط اطمینان حاصل می‌کند که بستهٔ sound.effects import شده است (احتمالاً هر کد مقداردهی اولیه در __init__.py را اجرا می‌کند) و سپس هر نامی را که در بسته تعریف شده است import می‌کند. این شامل هر نامی است که توسط __init__.py تعریف شده (و زیرماژول‌های بارگذاری‌شده به‌صراحت) است. همچنین شامل هر زیرماژولی از بسته است که توسط دستورات import قبلی به‌صراحت بارگذاری شده است. این کد را در نظر بگیرید:

>>> import sound.effects.echo
>>> import sound.effects.surround
>>> from sound.effects import *

در این مثال، ماژول‌های echo و surround در فضای نام جاری import می‌شوند زیرا زمانی که دستور from...import اجرا می‌شود، در بستهٔ sound.effects تعریف شده‌اند. (این همچنین زمانی که __all__ تعریف شده باشد کار می‌کند.)

اگرچه برخی از ماژول‌ها برای صادر کردن فقط نام‌هایی که از الگوهای خاصی پیروی می‌کنند زمانی که از import * استفاده می‌کنید، طراحی شده‌اند، اما همچنان در کد تولید (production code) به‌عنوان یک روش بد در نظر گرفته می‌شود.

به یاد داشته باشید، استفاده از from package import specific_submodule هیچ اشکالی ندارد! در واقع، این نماد (notation) توصیه‌شده است مگر اینکه ماژول واردکننده نیاز به استفاده از زیرماژول‌هایی با نام یکسان از بسته‌های مختلف داشته باشد.

۶.۴.۲. ارجاعات درون‌بسته‌ای (Intra-package References)

وقتی بسته‌ها به زیربسته‌ها ساختاردهی می‌شوند (مانند بستهٔ sound در مثال)، می‌توانید از importهای مطلق (absolute imports) برای ارجاع به زیرماژول‌های بسته‌های هم‌سطح (siblings) استفاده کنید. برای مثال، اگر ماژول sound.filters.vocoder نیاز به استفاده از ماژول echo در بستهٔ sound.effects داشته باشد، می‌تواند از from sound.effects import echo استفاده کند.

همچنین می‌توانید importهای نسبی (relative imports) را با فرم from module import name از دستور import بنویسید. این importها از نقطه‌های ابتدایی (leading dots) برای نشان دادن بسته‌های جاری و والد درگیر در import نسبی استفاده می‌کنند. برای مثال، از ماژول surround، ممکن است از:

from . import echo
from .. import formats
from ..filters import equalizer

استفاده کنید.

توجه داشته باشید که importهای نسبی بر اساس نام بستهٔ ماژول جاری هستند. از آنجا که ماژول اصلی (main module) دارای بسته نیست، ماژول‌هایی که برای استفاده به‌عنوان ماژول اصلی یک برنامهٔ پایتون در نظر گرفته شده‌اند، همیشه باید از importهای مطلق استفاده کنند.

۶.۴.۳. بسته‌ها در دایرکتوری‌های متعدد

بسته‌ها از یک ویژگی خاص دیگر به نام __path__ پشتیبانی می‌کنند. این ویژگی به‌عنوان دنباله‌ای از رشته‌ها شامل نام دایرکتوری حاوی __init__.py بسته، قبل از اجرای کد موجود در آن فایل، مقداردهی اولیه می‌شود. این متغیر قابل‌تغییر است؛ انجام این کار بر جست‌وجوهای آینده برای ماژول‌ها و زیربسته‌های موجود در بسته تأثیر می‌گذارد.

اگرچه این ویژگی اغلب مورد نیاز نیست، اما می‌توان از آن برای گسترش مجموعهٔ ماژول‌های موجود در یک بسته استفاده کرد.

پانویس‌ها

[1] در واقع تعاریف توابع نیز "دستوراتی" هستند که "اجرا" می‌شوند؛ اجرای یک تعریف تابع در سطح ماژول، نام تابع را به فضای نام سراسری ماژول اضافه می‌کند.