ماژول‌ها و پکیج‌های پایتون

امیرحسین بیگدلو 4 ماه قبل

در این مقاله در رابطه با دو موضوع ماژول و پکیج در پایتون صحبت میکنیم. با استفاده از دو است که میتوانید در پایتون به صورت ماژولار کدنویسی کنید.

 

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

 

 

 #  ماژول‌های پایتون

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

  1. یک ماژول میتواند در خود زبان پایتون نوشته شود.
  2. یک ماژول میتواند به زبان C نوشته شده و سپس در زمان اجرا به پایتون تبدیل شود مانند ماژول re پایتون
  3. یک ماژول میتواند به طور ذاتی در زبان وجود داشته باشد مثل ماژول itertools پایتون

 

مهم نیست با چه روش ماژول ساخته شود، برای استفاده فقط باید از دستور import استفاده کنید.

 

در این مقاله تمرکز ما روی روش اول است، ماژول‌هایی که مستقیما در پایتون نوشته شده‌اند. نکته مثبت در مورد این روش اینست که ساخت این نوع از ماژول‌ها بسیار ساده است. تنها کاری که باید بکنید اینست که فایلی با پسوند 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

 

چندین آبجکت درون این ماژول ساخته شده است:

  • s که یک رشته است.
  • a که یک لیست است.
  • ()foo که یک فانکشن است.
  • Foo که یک کلاس است.

 

فرض کنید میخواهید از ماژول mod.py استفاده کنید. در اینصورت باید از دستور import استفاده کنید:

>>> 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

 

زمانی که پایتون دستور import را اجرا میکند، در چند مسیر به دنبال آن میگردد:

  • ابتدا در همین دایرکتوری که اسکریپت را اجرا کرده‌اید، به دنبال ماژول میگردد.
  • سپس در دایرکتوری‌هایی که در متغیر PYTHONPATH قرار گرفته میگردد.
  • در آخر در مسیری که در زمان نصب پایتون مشخص شده میگردد.

 

همچنین برای دسترسی به مسیرهایی که پایتون در آن جستجو میکند، میتوانید از ماژول 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']

 

برای اینکه مطمئن شویم پایتون ماژول شما را پیدا میکند، باید یکی از کارهای زیر را انجام دهید:

  • ماژول mod.py را در همان مسیری که اسکریپت اجرایی هست، قرار دهید.
  • متغیر PYTHONPATH را تغییر دهید و مسیر mod.py را به آن اضافه کنید.

 

یک روش دیگری نیز وجود دارد که در آن میتوانید ماژول mod.py در هر کجا که خواستید قرار دهید اما در زمان اجار مسیر آنرا با ماژول sys به پایتون معرفی کنید:

>>> 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 پایتون

برای استفاده از ماژول‌ها باید از دستور import استفاده کنید. روش ‌های مختلفی برای استفاده از import وجود دارد که در ادامه به آنها اشاره میکنیم:

 

 

 +  import به همراه نام ماژول

ساده ترین روش همین است:

import <module_name>

 

دقت کنید که در این روش محتوای ماژول مستقیما در دسترس شما قرار نمیگیرد. در این حالت ماژول یک namepace ایجاد میکند که میتوانید از آن برای دسترسی به محتوای ماژول استفاده کنید. برای دسترسی به محتوای ماژول از نماد نقطه استفاده میشود که به این روش dot notation گفته میشود.

import mod

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

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

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

 

میوانید چندین ماژول را با یک دستور import وارد کنید فقط باید نام ماژول‌ها را با کاما جدا کنید:

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

 

 

 +  وارد کردن بخشی از ماژول با from

اگر به تمام محتوای یک ماژول نیاز نداشته باشید میتوانید از دستور from استفاده کنید. در این حالت فقط بخشی که نیاز دارید برای شما وارد میشود و نه بیشتر. شکل کلی آن به شکل زیر است:

from <module_name> import <name(s)>

 

مثلا در کد زیر ما فقط بخشی از ماژول mod را وارد میکنیم. دقت کنید که در صورت استفاده از این روش دیگر نیاز نیست از روش dot notation استفاده کنید و مستقیما میتوانید موارد وارد شده را استفاده کنید:

>>> 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>

 

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

>>> 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'>

 

روش ستاره به هیچ وجه پیشنهاد نمیشود چون ممکن است در پروژه‌های بزرگ در namespace شما تداخل ایجاد شود.

 

 

 +  مشخص کردن نام مستعار با as

در پایتون میتوانید یک ماژول را وارد کرده اما نام دیگر برای آن مشخص کنید. برای اینکار فقط کافیست از دستور as در مقابل import استفاده کنید:

>>> from mod import s as string, a as alist

>>> string
'If Comrade Napoleon says it, it must be right.'

>>> alist
[100, 200, 300]

 

در کد بالا ما s را به عنوان string و a را به عنوان alist وارد کردیم. در اینصورت برای استفاده از این موارد باید نام مستعار آنها را بکار ببرید.

 

در این بخش میتوانید به کل ماژول نیز نام مستعار بدهید. مثلا در کد پایین نام ماژول mod را به my_module تغییر داده‌ایم:

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

 

معمولا پیشنهاد میشود دستورات import را در بالای ماژول قرار دهید. اما شما میتوانید دستورات import را داخل فانکشن‌ها نیز قرار دهید:

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

>>> bar()
arg = corge

 

در آخر میتوانید از دستور try استفاده کنید که درصورت import نشدن یک ماژول بتوانید خطا را مدیریت کنید:

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

Module not found

 

 

 #  فانکشن dir پایتون

فانکشن dir تمام نام‌هایی که در namespace فعلی در دسترس هستند را برمیگرداند. اگر آرگومانی به آن ارسال نکنید نام‌های محدوده محلی را به ترتیب حروف الفبا مشخص میکند:

>>> 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 میتوانید برای فهمیدن اینکه چه مواردی با import اضافه شده‌اند، استفاده کنید:

>>> 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']

 

 

 #  اجرا یک ماژول به عنوان یک اسکریپت در پایتون

هر ماژول پایتونی که پسوند py. داشته باشد به عنوان یک اسکریپت پایتون نیز شناخته میشود. برای اجرای ماژول‌های پایتون فقط کافیست نام ماژول را در مقابل مفسر پایتون قرار دهید:

$ 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)

 

حالا اگر کد را اجرا کنیم در کنسول یک نتیجه نمایش داده خواهد شد:

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

 

اما اینجا یک مشکلی هست. اگر کد را در جایی دیگر import کنید، همان موارد را چاپ میکند:

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

 

این اصلا چیزی نیست که ما دلمان بخواهد. یک ماژول در زمان import شدن نباید چیزی چاپ کند.

 

آیا بهتر نبود به شکلی مشخص میکردیم که چه زمانی فایل به عنوان ماژول import شده و چه زمانی به شکل یک اسکریپت اجرا شده؟

 

این کار ممکن است.

 

زمانیکه یک فایل پایتونی به عنوان ماژول import میشود، پایتون به صورت اتوماتیک مقدار __name__ را برابر با نام ماژول قرار میدهد. اما اگر ماژول را به صورت مستقیم به عنوان یک اسکریپت اجرا کنید،‌ پایتون مقدار __name__ را برابر با __main__ میکند. با این روش میتوانید مشخص کنید که چه زمانی ماژول به عنوان اسکریپت اجرا شده و چه زمانی import شده است:

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)

 

حالا اگر اسکریپت را اجرا کنید نتیجه زیر را میبینید:

$ 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 کنید، نتیجه‌ای نمیبینید:

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

 

 

 #  بارگذاری مجدد ماژول‌های پایتون

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

 

کد زیر را در فایل 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 فقط در دفعه اول اجرا میشود و در دفعات دوم و سوم اجرا نمیشود. اگر تغییری در ماژول ایجاد کرده باشید یا باید کلا مفسر پایتون را ریستارت کنید یا از ماژول 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'>

 

 

 #  پکیج‌های پایتون

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

 

پکیج امکان دسته بندی ماژول‌ها را در پایتون میدهد. با استفاده از پکیج‌ها میتوانید یک ساختار سلسله مراتبی ایجاد کرده و از dot notation برای دسترسی به هر ماژول استفاده کنید. ساخت پکیج بسیار ساده است. فقط کافیست یک دایرکتوری با نام دلخواه ایجاد کرده و ماژول‌های پایتونی را در آن قرار دهید.

pkg/
├── mod1.py
└── mod2.py

 

مثلا در کد بالا یک پکیج به نام 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

 

در این حالت شما میتوانید با روش‌های مختلفی با دستور import به پکیج pkg دسترسی داشته باشید:

>>> import pkg.mod1, pkg.mod2
>>> pkg.mod1.foo()
[mod1] foo()
>>> x = pkg.mod2.Bar()
>>> x
<pkg.mod2.Bar object at 0x033F7290>

 

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

 

>>> from pkg.mod2 import Bar as Qux
>>> x = Qux()
>>> x
<pkg.mod2.Bar object at 0x036DFFD0>

 

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

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

 

در واقع میتوانید خود پکیج را نیز import کنید:

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

 

اما این روش آنچنان کارآمد نیست. چون نمیتوانید به محتویات ماژول‌ها دسترسی داشته باشید:

>>> 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__ ایجاد کنید، هر زمان که پکیج یا ماژولی از پکیج import شود، کدهای داخل فایل __init__ اجرا خواهد شد. مثلا فرض کنید کد زیر را در فایل پایتونی __init__ نوشته‌ایم:

# __init__.py

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

 

حالا دایرکتوی ما به این شکل است:

pkg/
├── __init__.py
├── mod1.py
└── mod2.py

 

حالا اگر پکیج pkg را import کنید، کدهای فایل __init__ یه طور اتوماتیک اجرا میشود:

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

 

 

 #  import کردن با * از پکیج

ساختار پکیج را کمی تغییر داده و به شکل زیر در می‌آید:

pkg/
├── mod1.py
├── mod2.py
├── mod3.py
└── mod4.py

 

ما چهار ماژول در پکیج 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

 

در بخش‌های قبل دیدید که import با ستاره چطور کار میکند. استفاده از ستاره تمام محتویات ماژول‌ها را import میکند به جز آنهایی که با underscore شروع میشوند:

>>> 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__']

 

اتفاق خاصی نمیفتد. ممکن است فکر میکردید که پایتون تمام ماژول‌هایی که در آن پکیج وجود داشته را برای شما import میکند. اما اینطور نشد.

 

اگر داخل پکیج فایلی به نام __init__ وجود داشته باشد و در داخل آن فایل لیست به نام __all__ باشد، پایتون محتویات __all__ را وارد میکند و نه بیشتر. برای مثال، فرض کنید یک فایل به نام __init__ داخل پکیج pkg ایجاد کرده ایم و کدهای زیر را در آن مینویسیم:

# pkg/__init__.py

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

 

حالا اگر پکیج pkg را با ستاره import کنید، تمام مواردی که در __all__ نوشته بودید، import خواهد شد:

>>> 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'>

 

استفاده از ستاره هنوز هم پیشنهاد نمیشود اما در این حالت حداقل به سازنده ماژول این امکان را میدهد که چه چیزهایی در زمان استفاده از ستاره import شوند.

 

 

 #  زیرپکیج‌های پایتون

در پایتون پکیج‌ها میتوانند تودرتو باشند یعنی یک پکیج در داخل خود چند پکیج دیگر داشته باشد. به طور مثال ما ساختار دایرکتوری خود را به شکل زیر تغییر داده‌ایم:

pkg/
├── sub_pkg1
│   ├── mod1.py
│   └── mod2.py
└── sub_pkg2
    ├── mod3.py
    └── mod4.py

 

هنوز هم چهار ماژول داریم اما به جای اینکه همه آنها در pkg باشند، در بین زیر پکیج‌های sub_pkg1 و sub_pkg2 تقسیم شده‌اند. importها هنوز هم مانند قبل کار میکنند اما باید یک dot notation اضافی بزنید تا وارد زیر پکیج‌ها شوید:

>>> 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 از ماژول mod1 را در ماژول mod3 استفاده کنید:

# 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()

 

 

 #  نتیجه گیری

در این مقاله یاد گرفتید که:

  • چطور یک ماژول پایتونی بسازید.
  • چه مکان‌هایی را پایتون برای یافتن ماژول جستجو میکند.
  • چطور از دستور import استفاده کنید.
  • چطور ماژول‌های پایتون را به عنوان اسکریپت اجرا کنید.
  • چطور ماژول‌ها را در پکیج و زیرپکیج دسته بندی کنید.

 

امیدواریم این مقاله برای شما مفید بوده باشد.

مطالب مشابه



مونگارد