ماژولها و بستهها
در فصل قبل از ماژولهای آماده پایتون استفاده کردیم. حالا ماژول خودمان را میسازیم و یاد میگیریم چطور یک برنامه بزرگ را روی چند فایل پخش کنیم.
۱. ماژول فقط یک فایل است
خبر خوب: ماژول چیز خاصی نیست. هر فایل
py. که بنویسید، یک ماژول است و میشود از فایل دیگری
واردش کرد.
'''A tiny module: just a .py file containing functions we want to reuse.'''
def add(num1: float, num2: float) -> float:
'Return the sum of two numbers.'
return num1 + num2
فایل math_module.py
# 'import math_module' loads the file math_module.py that sits next to this one.
# The functions are then reached through the module name.
import math_module
if __name__ == '__main__':
result = math_module.add(num1=5, num2=6)
print(result)
فایل app.py: نمونه 01_import_module
شرح مثال
- نام ماژول همان نام فایل است، بدون پسوند
py.. - پایتون فایل را در پوشهای که برنامه از آن اجرا شده جستوجو میکند، بعد در مسیرهای نصب پایتون.
- بعد از
import، هر چیزی که در آن فایل تعریف شده از راه نام ماژول در دسترس است. - برای اجرای این نمونهها باید ترمینال را داخل همان پوشه باز کنید و
python app.pyبزنید.
۲. وارد کردن با نام مستعار
# A module can be renamed while importing it. This is useful when the real
# name is long (you will see 'import pandas as pd' in real projects).
import math_module as m
if __name__ == '__main__':
result = m.add(num1=5, num2=6)
print(result)
نام مستعار: نمونه 02_import_as
در پروژههای واقعی نامهای مستعار قراردادی وجود دارد که همه رعایت میکنند: import numpy as np، import pandas as pd، import tkinter as tk. این موارد را در فصلهای بعد میبینید.
۳. وارد کردن مستقیم یک نام
# 'from ... import ...' brings the name directly into our file,
# so we call it without the module prefix.
from math_module import add
if __name__ == '__main__':
result = add(num1=5, num2=6)
print(result)
وارد کردن مستقیم: نمونه 03_from_import
این شکل کد را کوتاهتر میکند ولی یک هزینه دارد: وقتی در وسط فایل
add(...) را میبینید، معلوم نیست تابع خودتان است یا از
جایی آمده است. برای فایلهای کوچک اشکالی ندارد.
۴. وارد کردن چند نام
# Several names can be imported in one line.
from math_module import add, mul
# 'from math_module import *' would import everything, but then the reader of
# the file no longer knows where 'add' comes from. Avoid it.
if __name__ == '__main__':
print(add(num1=5, num2=6))
print(mul(num1=5, num2=6))
چند نام: نمونه 04_import_several
۵. پوشه __pycache__
اولین باری که ماژولی را وارد کنید، پوشهای به نام
__pycache__ کنار فایلهایتان ظاهر میشود. نگران نباشید:
- پایتون نسخه ترجمهشده ماژول را آنجا نگه میدارد تا دفعه بعد سریعتر بارگذاری شود.
- خودکار ساخته میشود و هر وقت بخواهید میشود پاکش کرد.
- بخشی از کد شما نیست و نباید در گیت ذخیره شود. در فصل سی و نهم میبینید که در فایل
.gitignoreاولین چیزی که مینویسند همین است.
۶. بسته چیست
وقتی ماژولها زیاد شوند، باید دستهبندیشان کرد.
بسته یک پوشه است که داخلش فایلی به نام
__init__.py دارد.
05_package/
app.py
math_package/
__init__.py
my_math.py
# A package is a FOLDER that contains an __init__.py file.
# The path to a module inside it uses dots: package.module
from math_package.my_math import add, mul
if __name__ == '__main__':
print(add(num1=5, num2=6))
print(mul(num1=5, num2=6))
استفاده از بسته: نمونه 05_package
شرح مثال
- فایل
__init__.pyمیتواند کاملاً خالی باشد. صرف وجودش، پوشه را به بسته تبدیل میکند. - مسیر ماژول داخل بسته با نقطه نوشته میشود:
math_package.my_math. - بسته میتواند بستههای دیگری هم داخلش داشته باشد و هر قدر بخواهید تودرتو شود.
۷. چند ماژول در یک بسته
# Two modules of the same package, each imported with its own path.
from math_package.my_math import add, mul
from math_package.your_math import sub, div
if __name__ == '__main__':
print(add(num1=5, num2=6))
print(sub(num1=5, num2=6))
print(div(num1=5, num2=6))
دو ماژول: نمونه 06_package_two_modules
'''A second module in the same package.'''
def sub(num1: float, num2: float) -> float:
'Return the difference of two numbers.'
return num1 - num2
def div(num1: float, num2: float) -> float:
'Return the quotient of two numbers.'
if num2 == 0:
raise ZeroDivisionError('num2 must not be zero')
return num1 / num2
فایل your_math.py
به تابع div دقت کنید: به جای اینکه پیام خطا چاپ کند، خطا
میاندازد. این کار درست یک تابع کتابخانهای است. تابع
نباید تصمیم بگیرد که پیام خطا را چطور به کاربر نشان دهد؛ آن وظیفه
برنامه اصلی است. دستور raise موضوع فصل نوزدهم است.
۸. بازصدور در __init__.py
مشکل بخش قبل این است که کاربر بسته باید بداند هر تابع در کدام ماژول
است. با نوشتن چند خط در __init__.py این را حل میکنیم.
# __init__.py runs when the package is imported. Re-exporting the names here
# gives the user of the package a single, simple entry point:
# from math_package import add, sub
#
# The dot in '.my_math' means "the module my_math of THIS package"
# (a relative import).
from .my_math import add, mul
from .your_math import sub, div
فایل __init__.py: نمونه 07_package_init
# Thanks to the re-exports in __init__.py, we no longer have to know
# in which module of the package each function lives.
from math_package import add, mul, sub, div
if __name__ == '__main__':
print(add(num1=5, num2=6))
print(div(num1=5, num2=6))
استفاده سادهشده: نمونه 07_package_init
شرح مثال
- محتوای
__init__.pyهر بار که بسته وارد شود اجرا جستوجو میکند. - نقطه پیش از نام ماژول یعنی ایمپورت نسبی: «ماژول my_math از همین بسته». بدون نقطه، پایتون دنبال ماژولی سراسری به آن نام جستوجو میکند.
- حالا کاربر بسته فقط مینویسد
from math_package import addو ضرورتی ندارد ساختار داخلی بسته را بداند. - این همان کاری است که کتابخانههای بزرگ میکنند: شما مینویسید
from datetime import dateبیآنکه بدانیدdateدر کدام فایل تعریف شده است.
۹. کلاس داخل بسته
'''The Animal class (classes are studied in detail in session 21).'''
class Animal:
def __init__(self, name: str, weight: int, is_pet: bool = False) -> None:
self.name = name
self.weight = weight
self.is_pet = is_pet
def __str__(self) -> str:
return self.name
فایل animals/animal.py: نمونه 08_class_in_a_package
# A package usually exports classes, not only functions.
from animals import Animal
if __name__ == '__main__':
the_cat = Animal('Tom', 7, is_pet=True)
print(the_cat)
the_wolf = Animal('Fang', 20)
print(the_wolf)
استفاده از کلاس: نمونه 08_class_in_a_package
کلاس موضوع فصل بیست و یکم است، ولی نکته این بخش ربطی به کلاس ندارد:
قرارداد رایج این است که هر کلاس در فایل خودش باشد و
__init__.py آن را بازصادر کند. در پروژه پایانی فصل سی و نهم
دقیقاً همین ساختار را میبینید.
۱۰. چرا نگهبان اجرا لازم است
حالا میفهمیم آن خط if __name__ == '__main__': که از فصل
دوازدهم مینویسیم واقعاً به چه درد میخورد.
'''This module can be RUN on its own and also be IMPORTED.'''
class Animal:
def __init__(self, name: str, weight: int, is_pet: bool = False) -> None:
self.name = name
self.weight = weight
self.is_pet = is_pet
def __str__(self) -> str:
return self.name
# Everything below runs only when this file is started directly
# (python animals/animal.py). Without this guard, the two animals would also
# be created and printed every time app.py imports the module.
if __name__ == '__main__':
the_cat = Animal('Tom from the module', 7)
print(the_cat)
the_wolf = Animal('Fang from the module', 20)
print(the_wolf)
نگهبان اجرا در ماژول: نمونه 09_module_main_guard
اول python animals/animal.py را اجرا کنید: کد آزمایشی
ماژول اجرا میشود. بعد python app.py را اجرا کنید:
فقط حیوانهای خود app.py چاپ میشوند. حالا خط نگهبان
را بردارید و دوباره app.py را اجرا کنید. میبینید که
حیوانهای ماژول هم چاپ میشوند، در حالی که کسی آنها را نخواسته بود.
این یعنی هر ماژولی میتواند کد آزمایشی خودش را داشته باشد بدون اینکه مزاحم کسی شود. عادت خوبی است که هر ماژولی که مینویسید، در انتهایش یک بلوک نگهبان با چند نمونه استفاده داشته باشد.
۱۱. ماژول تنظیمات
یک کاربرد بسیار عملی ماژول: همه مقدارهای ثابت برنامه را در یک فایل جدا بگذارید.
'''All the settings of the program in one place.
Constants are written in CAPITALS. Keeping them in their own module means that
changing a message or a limit never requires touching the logic of the program.
'''
APP_NAME: str = 'Kadoos Shop'
CURRENCY: str = 'Toman'
VAT_RATE: float = 0.09
MAX_ITEMS: int = 10
WELCOME_MESSAGE: str = 'Welcome to {app}!'
GOODBYE_MESSAGE: str = 'Thank you, see you soon.'
PRODUCTS: dict[str, int] = {
'Notebook': 15_000,
'Pencil': 5_000,
'Eraser': 3_000,
}
فایل constants.py: نمونه 10_constants_module
'''A small shop that reads every setting from constants.py.'''
import constants
def show_products() -> None:
'Print the price list.'
for name, price in constants.PRODUCTS.items():
print(f'{name:<10}{price:>10,} {constants.CURRENCY}')
def price_with_vat(price: float) -> float:
'Return the price including VAT.'
return price * (1 + constants.VAT_RATE)
def main() -> None:
print(constants.WELCOME_MESSAGE.format(app=constants.APP_NAME))
show_products()
product = input('Which product do you want? ')
if product in constants.PRODUCTS:
total = price_with_vat(constants.PRODUCTS[product])
print(f'With VAT: {total:,.0f} {constants.CURRENCY}')
else:
print('We do not sell that.')
print(constants.GOODBYE_MESSAGE)
if __name__ == '__main__':
main()
استفاده از تنظیمات: نمونه 10_constants_module
شرح مثال
- برای تغییر نرخ مالیات یا متن خوشآمدگویی، فقط یک فایل عوض میشود و به منطق برنامه دست نمیزنید.
- اینجا
import constantsبه کار رفته نهfrom constants import *. با این کار در متن برنامه پیداست که هر مقدار از کجا آمده است. - متد
formatشکل قدیمیتر قالببندی است. اینجا لازم است، چون متن پیام در فایل دیگری است و آنجا هنوز نمیدانیم نام برنامه چیست.
رمز عبور، توکن ربات و کلید API را هرگز در constants.py نگذارید، چون آن فایل معمولاً در گیت ذخیره میشود و روی اینترنت میرود. این موارد باید از متغیر محیطی خوانده شوند. در فصل سی و هفتم که ربات پیامرسان مینویسیم، دقیقاً روی همین موضوع مکث میکنیم، چون در نسخه اصلی این دوره یک توکن واقعی داخل کد رها شده بود.
پوشه S17-modules-and-packages شامل ده پوشه نمونه، از
01_import_module تا 10_constants_module.
هر کدام را باید از داخل خودش اجرا کنید.
خلاصه فصل
| مورد | توضیح |
|---|---|
| ماژول | هر فایل py. |
| بسته | پوشهای که __init__.py دارد |
import m | وارد کردن کامل، استفاده با m.name |
import m as x | نام مستعار |
from m import a, b | وارد کردن مستقیم چند نام |
from p.m import a | ماژول داخل بسته، با نقطه |
from .m import a | ایمپورت نسبی، داخل خود بسته |
__init__.py | هنگام وارد کردن بسته اجرا میشود. جای بازصدور |
__pycache__ | نسخه ترجمهشده. قابل پاک کردن، نباید در گیت باشد |
| نگهبان اجرا | کد آزمایشی ماژول هنگام وارد شدن اجرا نشود |
constants.py | همه تنظیمات ثابت در یک جا. بدون رمز و توکن |
تمرینهای فصل هفده
- ماژول
geometry.pyبا توابعcircle_areaوrectangle_areaوtriangle_areaبسازید و از یکapp.pyاستفادهاش کنید. - همان ماژول را به هر سه شکل وارد کنید و مقایسه کنید.
geometry.pyرا به بستهgeometry/با دو ماژولflat.pyوsolid.pyتبدیل کنید.- در
__init__.pyآن بسته، همه چیز را بازصادر کنید. - به هر ماژولتان یک بلوک آزمایشی با نگهبان اجرا اضافه کنید و ببینید هنگام وارد شدن اجرا نمیشود.
- برای یکی از برنامههای قبلیتان یک
constants.pyبسازید و همه متنها و عددهای ثابت را به آن منتقل کنید. - مقدار
__name__را هم در ماژول و هم در برنامهای که آن را وارد میکند چاپ کنید و تفاوت را توضیح دهید.