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

ماژول و پکیج در پایتون: import، from، as و ساخت ماژول شخصی

۵ دقیقه مطالعه · به‌روزرسانی ۲۲ مهر ۱۴۰۵

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

ماژول یک فایل ‎.py‎ است که مجموعه‌ای از تعریف‌ها (توابع، متغیرها، …) را در خود دارد. هر فایل پایتونی که بنویسید خودبه‌خود یک ماژول است.

ساختن یک ماژول

فرض کنید فایل circle.py این محتوا را دارد:

pi = 3.14159

def area(radius):
    return pi * (radius ** 2)

def circumference(radius):
    return 2 * pi * radius

همین! حالا یک ماژول به اسم circle دارید. کافی است این فایل کنار نوت‌بوک یا فایلی باشد که می‌خواهد از آن استفاده کند.

⚠️ پسوند باید ‎.py‎ باشد. اگر در ویندوز فایل را با Notepad ساخته‌اید، مطمئن شوید اسمش circle.py.txt نشده است (نمایش پسوندها را از منوی View روشن کنید).

در مثال‌های این صفحه، چون در مرورگر هستیم، اول با چند خط پایتون خود فایل circle.py را می‌سازیم (نوشتن در فایل را در درس فایل‌ها یاد می‌گیریم). روی کامپیوتر خودتان کافی است فایل را با ویرایشگر بسازید.

روش ۱: import

# ساختن circle.py (روی سیستم خودتان این بخش لازم نیست)
with open("circle.py", "w") as f:
    f.write("""pi = 3.14159

def area(radius):
    return pi * (radius ** 2)

def circumference(radius):
    return 2 * pi * radius
""")

import circle

pi = 3
print(pi)
print(circle.pi)
print(circle.area(3))
print(circle.circumference(3))

خروجی:

3
3.14159
28.27431
18.849539999999998

با import circle به همه‌چیز داخل ماژول از طریق نام ماژول و نقطه دسترسی داریم: circle.pi، circle.area. مهم‌تر از همه: pi ما (۳) و circle.pi (۳٫۱۴۱۵۹) با هم قاطی نمی‌شوند؛ هر کدام در فضای نام خودش است.

💡 آن عدد عجیب 18.849539999999998 همان محدودیت اعداد اعشاری است.

روش ۲: import … as (نام مستعار)

اگر اسم ماژول طولانی است:

import circle as c
print(c.area(12))
print(c.circumference(12))

(این کد از circle.py ساخته‌شده در مثال قبل استفاده می‌کند؛ اگر خطای ModuleNotFoundError گرفتید، اول آن را اجرا کنید.)

این روش در دنیای پایتون بسیار رایج است: import numpy as np، import pandas as pd.

روش ۳: from … import

اگر فقط به چند چیز مشخص نیاز دارید، آن‌ها را مستقیم بیاورید. دیگر لازم نیست نام ماژول را جلویشان بنویسید:

from circle import area
print(area(12))

روش ۴: from … import * (توصیه نمی‌شود)

from circle import *
print(pi)
print(area(3))

این کار همه‌چیز داخل ماژول را مستقیم در فضای نام فعلی می‌ریزد. سریع است، ولی کار ناپسندی است:

  • اگر خودتان متغیری به اسم pi داشته باشید، بی‌صدا با pi ماژول جایگزین می‌شود.
  • اگر دو ماژول هر دو تابعی به اسم area داشته باشند (مثلاً circle و rectangle)، معلوم نیست کدام را صدا می‌زنید.
  • کسی که کد شما را می‌خواند نمی‌فهمد area از کجا آمده.

💡 قاعده: یا import module (و همه‌جا module.name بنویسید)، یا from module import name1, name2 (صریح و مشخص). از * دوری کنید.

نکته‌های مهم

ماژول فقط یک بار اجرا می‌شود

دستورات داخل یک ماژول فقط اولین باری که import می‌شود اجرا می‌شوند. اگر بعد از import، فایل circle.py را ویرایش کنید و دوباره import circle بزنید، تغییرات دیده نمی‌شوند؛ پایتون نسخهٔ قبلی را از حافظه برمی‌دارد.

در Jupyter راه‌حل این است: Kernel › Restart Kernel. این کار حافظه را کامل پاک می‌کند و مطمئن می‌شوید چیزی از قبل در حافظه گیر نکرده است.

💡 بعد از ری‌استارت، اگر بنویسید circle. و Tab بزنید و پیشنهادی نیاید، یعنی واقعاً پاک شده و باید دوباره import کنید.

پایتون کجا دنبال ماژول می‌گردد؟

اول پوشهٔ فعلی (کنار فایل یا نوت‌بوک)، بعد کتابخانهٔ استاندارد، بعد پکیج‌هایی که با pip نصب کرده‌اید. اگر پیدا نکرد، ModuleNotFoundError می‌گیرید.

⚠️ اسم فایلتان را هم‌اسم ماژول‌های معروف نگذارید. اگر فایلی به اسم random.py یا math.py در پوشه‌تان بسازید، پایتون به‌جای ماژول استاندارد، فایل شما را import می‌کند و همه‌چیز عجیب خراب می‌شود.

پکیج: ماژول‌ها در پوشه

وقتی ماژول‌ها زیاد شدند، آن‌ها را در پوشه دسته‌بندی کنید. پوشه‌ای که ماژول دارد پکیج نام دارد:

my_project/
├── main.ipynb
└── shapes/
    ├── __init__.py      ← (خالی) یعنی این پوشه یک پکیج است
    ├── circle.py
    └── rectangle.py
import os
os.makedirs("shapes", exist_ok=True)
open("shapes/__init__.py", "w").close()
with open("shapes/circle.py", "w") as f:
    f.write("pi = 3.1415\ndef masahat(r):\n    return pi * r ** 2\ndef mohit(r):\n    return 2 * pi * r\n")
with open("shapes/rectangle.py", "w") as f:
    f.write("def area(w, h):\n    return w * h\ndef mohit(w, h):\n    return 2 * (w + h)\n")

from shapes import circle as c
from shapes import rectangle as r

print(c.mohit(12))     # محیط دایره
print(r.mohit(12, 4))  # محیط مستطیل

هر دو ماژول تابعی به اسم mohit دارند، ولی چون از طریق نام ماژول (c. و r.) صدایشان می‌زنیم، هیچ ابهامی پیش نمی‌آید. پکیج‌ها می‌توانند زیرپوشه هم داشته باشند (security/encryption/symmetric.py ← import security.encryption.symmetric).

💡 فایل __init__.py در پایتون ۳ اجباری نیست، ولی گذاشتنش (حتی خالی) عادت خوبی است و جلوی رفتارهای غیرمنتظره را می‌گیرد.

if __name__ == “__main__”

گاهی می‌خواهید فایلی هم ماژول باشد (دیگران import کنند) و هم مستقیم اجرا شود (مثلاً برای تست). اما یادتان هست که موقع import، کل فایل اجرا می‌شود؟ پس تست‌هایتان هم اجرا می‌شوند! راه‌حل:

# circle.py
pi = 3.14159

def area(radius):
    return pi * radius ** 2

if __name__ == "__main__":
    # فقط وقتی اجرا می‌شود که فایل مستقیم اجرا شود: python circle.py
    # نه وقتی که import می‌شود
    print("test:", area(1))

__name__ متغیری است که پایتون خودش مقداردهی می‌کند: وقتی فایل مستقیم اجرا شود "__main__" است، و وقتی import شود، اسم ماژول ("circle").

کتابخانهٔ استاندارد

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

import math
import random
from datetime import date

print(math.sqrt(2), math.pi)
print(random.randint(1, 6))        # تاس
print(date.today())

تمرین

یک ماژول به اسم rectangle بسازید که دو تابع داشته باشد: area(w, h) برای مساحت و perimeter(w, h) برای محیط مستطیل. برای هر دو docstring بنویسید. بعد در کد اصلی import کنید و استفاده کنید.

جواب

فایل rectangle.py:

def area(w, h):
    """
    مساحت مستطیل

    Args:
        w: عرض
        h: ارتفاع
    """
    return w * h

def perimeter(w, h):
    """محیط مستطیل"""
    return 2 * (w + h)

کد اصلی:

import rectangle as r
print(r.area(2, 7))         # 14
print(r.perimeter(12, 5))   # 34

from rectangle import area
print(area(2, 7))
help(area)                  # docstring را نشان می‌دهد